summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/README_js-mainline.md22
-rw-r--r--api/test-current.txt2
-rw-r--r--core/java/android/app/AppCompatCallbacks.java11
-rw-r--r--core/java/android/content/res/AssetManager.java15
-rw-r--r--core/java/android/os/Handler.java4
-rw-r--r--core/java/android/os/TraceNameSupplier.java31
-rw-r--r--core/java/android/os/storage/StorageManager.java5
-rw-r--r--core/java/android/provider/Settings.java13
-rw-r--r--core/java/android/service/textclassifier/TextClassifierService.java4
-rw-r--r--core/java/android/view/CompositionSamplingListener.java22
-rw-r--r--core/java/android/view/SurfaceView.java11
-rw-r--r--core/java/android/widget/SimpleMonthView.java1
-rw-r--r--core/java/com/android/internal/compat/ChangeReporter.java102
-rw-r--r--core/java/com/android/internal/infra/ServiceConnector.java5
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java13
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java2
-rwxr-xr-xcore/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java36
-rw-r--r--core/java/com/android/internal/util/function/pooled/PooledRunnable.java5
-rw-r--r--core/jni/Android.bp5
-rwxr-xr-xcore/jni/android/graphics/Bitmap.cpp14
-rw-r--r--core/jni/android/graphics/Bitmap.h2
-rw-r--r--core/jni/android/graphics/apex/TypeCast.h70
-rw-r--r--core/jni/android/graphics/apex/android_bitmap.cpp106
-rw-r--r--core/jni/android/graphics/apex/android_canvas.cpp55
-rw-r--r--core/jni/android/graphics/apex/android_paint.cpp47
-rw-r--r--core/jni/android/graphics/apex/include/android/graphics/bitmap.h102
-rw-r--r--core/jni/android/graphics/apex/include/android/graphics/canvas.h88
-rw-r--r--core/jni/android/graphics/apex/include/android/graphics/paint.h66
-rw-r--r--core/jni/android/opengl/util.cpp159
-rw-r--r--core/jni/android_graphics_GraphicBuffer.cpp10
-rw-r--r--core/jni/android_util_AssetManager.cpp19
-rw-r--r--core/jni/android_view_PointerIcon.cpp6
-rw-r--r--core/jni/android_view_PointerIcon.h6
-rw-r--r--core/jni/android_view_Surface.cpp11
-rw-r--r--core/jni/android_view_TextureLayer.cpp3
-rw-r--r--core/jni/android_view_TextureView.cpp10
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp6
-rw-r--r--data/etc/services.core.protolog.json4
-rw-r--r--libs/androidfw/AssetManager2.cpp58
-rw-r--r--libs/androidfw/include/androidfw/AssetManager2.h6
-rw-r--r--libs/androidfw/tests/AssetManager2_test.cpp8
-rw-r--r--libs/input/Android.bp2
-rw-r--r--libs/input/PointerController.cpp6
-rw-r--r--libs/input/SpriteController.cpp51
-rw-r--r--libs/input/SpriteController.h18
-rw-r--r--libs/input/tests/Android.bp2
-rw-r--r--opengl/java/android/opengl/GLUtils.java30
-rw-r--r--packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDownloader.java47
-rw-r--r--packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupFileDecryptorTask.java378
-rw-r--r--packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTask.java87
-rw-r--r--packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/MalformedEncryptedFileException.java24
-rw-r--r--packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/MessageDigestMismatchException.java27
-rw-r--r--packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/UnsupportedEncryptedFileException.java28
-rw-r--r--packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/BackupFileDecryptorTaskTest.java583
-rw-r--r--packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTaskTest.java128
-rw-r--r--packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java49
-rw-r--r--packages/BackupEncryption/test/robolectric/src/com/android/server/testing/TestFileUtils.java31
-rw-r--r--packages/SettingsLib/res/values/strings.xml4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java9
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java15
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java2
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java3
-rw-r--r--packages/Shell/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values/strings.xml2
-rw-r--r--packages/SystemUI/res/layout/status_bar_expanded_plugin_frame.xml6
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIBinder.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerUI.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java32
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/SaveUi.java7
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java7
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java2
-rw-r--r--services/core/java/com/android/server/compat/PlatformCompat.java9
-rw-r--r--services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java20
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java1
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java68
-rw-r--r--services/core/java/com/android/server/rollback/AppDataRollbackHelper.java1
-rw-r--r--services/core/java/com/android/server/rollback/Rollback.java325
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java355
-rw-r--r--services/core/java/com/android/server/rollback/RollbackStore.java8
-rw-r--r--services/core/java/com/android/server/tv/TvRemoteProviderProxy.java431
-rw-r--r--services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java10
-rw-r--r--services/core/java/com/android/server/tv/TvRemoteService.java234
-rw-r--r--services/core/java/com/android/server/tv/TvRemoteServiceInput.java244
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java12
-rw-r--r--services/core/jni/com_android_server_VibratorService.cpp70
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp18
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java39
-rw-r--r--services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java249
-rw-r--r--tests/FlickerTests/AndroidManifest.xml2
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt4
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt2
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt2
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt26
-rw-r--r--wifi/java/android/net/wifi/WifiConfiguration.java20
-rw-r--r--wifi/java/android/net/wifi/WifiScanner.java69
-rw-r--r--wifi/tests/src/android/net/wifi/WifiConfigurationTest.java49
-rw-r--r--wifi/tests/src/android/net/wifi/WifiScannerTest.java34
124 files changed, 3768 insertions, 1612 deletions
diff --git a/apex/jobscheduler/README_js-mainline.md b/apex/jobscheduler/README_js-mainline.md
index c1ad666e3e05..ea20e3e29d99 100644
--- a/apex/jobscheduler/README_js-mainline.md
+++ b/apex/jobscheduler/README_js-mainline.md
@@ -1,23 +1,11 @@
# Making Job Scheduler into a Mainline Module
-## TODOs
-
-See also:
-- http://go/moving-js-code-for-mainline
-- http://go/jobscheduler-code-dependencies-2019-07
-
-- [ ] Move this into `frameworks/apex/jobscheduler/...`. Currently it's in `frameworks/base/apex/...`
-because `frameworks/apex/` is not a part of any git projects. (and also working on multiple
-projects is a pain.)
-
## Current structure
- JS service side classes are put in `jobscheduler-service.jar`.
It's *not* included in services.jar, and instead it's put in the system server classpath,
which currently looks like the following:
-`SYSTEMSERVERCLASSPATH=/system/framework/services.jar:/system/framework/jobscheduler-service.jar:/system/framework/ethernet-service.jar:/system/framework/wifi-service.jar:/system/framework/com.android.location.provider.jar`
-
- (Note `jobscheduler-service.jar` will be put at the end in http://ag/9128109)
+`SYSTEMSERVERCLASSPATH=/system/framework/services.jar:/system/framework/ethernet-service.jar:/system/framework/com.android.location.provider.jar:/system/framework/jobscheduler-service.jar`
`SYSTEMSERVERCLASSPATH` is generated from `PRODUCT_SYSTEM_SERVER_JARS`.
@@ -29,10 +17,4 @@ as of http://ag/9145619.
`framework.jar` merging the two jar files, and this jar file is what's
put on the device and loaded by Zygote.
-
-This is *not* the final design. From a gerrit comment on http://ag/9145619:
-
-> This CL is just the first step, and the current state isn't not really the final form. For now we just want to have two separate jars, which makes it easier for us to analyze dependencies between them, and I wanted to minimize the change to the rest of the system. So, for example, zygote will still only have "framework.jar" in its classpath, instead of the two jars for now.
-> But yes, eventually, we won't even be able to have the monolithic "framework.jar" file because of mainline, so we need to figure out how to build the system without creating it. At that point zygote will have the two separate jar files in its classpath.
-> When we reach that point, we should revisit the naming of it, and yes, maybe the simple "framework.jar" is a good option.
-> But again, for now, I want to make this change as transparent as possible to the rest of the world.
+The current structure is *not* the final design.
diff --git a/api/test-current.txt b/api/test-current.txt
index 75d80bd9eeaf..9b00a42d27a9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -786,7 +786,7 @@ package android.content.res {
public final class AssetManager implements java.lang.AutoCloseable {
method @NonNull public String[] getApkPaths();
- method @Nullable public java.util.Map<java.lang.String,java.lang.String> getOverlayableMap(String);
+ method @Nullable public String getOverlayablesToString(String);
}
public final class Configuration implements java.lang.Comparable<android.content.res.Configuration> android.os.Parcelable {
diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java
index 08c97eb284e3..19d158dedd06 100644
--- a/core/java/android/app/AppCompatCallbacks.java
+++ b/core/java/android/app/AppCompatCallbacks.java
@@ -18,7 +18,6 @@ package android.app;
import android.compat.Compatibility;
import android.os.Process;
-import android.util.Log;
import android.util.StatsLog;
import com.android.internal.compat.ChangeReporter;
@@ -31,8 +30,6 @@ import java.util.Arrays;
* @hide
*/
public final class AppCompatCallbacks extends Compatibility.Callbacks {
- private static final String TAG = "Compatibility";
-
private final long[] mDisabledChanges;
private final ChangeReporter mChangeReporter;
@@ -48,7 +45,8 @@ public final class AppCompatCallbacks extends Compatibility.Callbacks {
private AppCompatCallbacks(long[] disabledChanges) {
mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length);
Arrays.sort(mDisabledChanges);
- mChangeReporter = new ChangeReporter();
+ mChangeReporter = new ChangeReporter(
+ StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__APP_PROCESS);
}
protected void reportChange(long changeId) {
@@ -67,10 +65,7 @@ public final class AppCompatCallbacks extends Compatibility.Callbacks {
private void reportChange(long changeId, int state) {
int uid = Process.myUid();
- //TODO(b/138374585): Implement rate limiting for the logs.
- Log.d(TAG, ChangeReporter.createLogString(uid, changeId, state));
- mChangeReporter.reportChange(uid, changeId,
- state, /* source */StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__APP_PROCESS);
+ mChangeReporter.reportChange(uid, changeId, state);
}
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 2420a6109155..567e26b4c2f6 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1376,7 +1376,6 @@ public final class AssetManager implements AutoCloseable {
/**
* @hide
*/
- @TestApi
@GuardedBy("this")
public @Nullable Map<String, String> getOverlayableMap(String packageName) {
synchronized (this) {
@@ -1385,6 +1384,18 @@ public final class AssetManager implements AutoCloseable {
}
}
+ /**
+ * @hide
+ */
+ @TestApi
+ @GuardedBy("this")
+ public @Nullable String getOverlayablesToString(String packageName) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetOverlayablesToString(mObject, packageName);
+ }
+ }
+
@GuardedBy("this")
private void incRefsLocked(long id) {
if (DEBUG_REFS) {
@@ -1504,6 +1515,8 @@ public final class AssetManager implements AutoCloseable {
private static native String[] nativeCreateIdmapsForStaticOverlaysTargetingAndroid();
private static native @Nullable Map nativeGetOverlayableMap(long ptr,
@NonNull String packageName);
+ private static native @Nullable String nativeGetOverlayablesToString(long ptr,
+ @NonNull String packageName);
// Global debug native methods.
/**
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 9af9edae9a3f..45a9cf5ab869 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -295,6 +295,10 @@ public class Handler {
/** {@hide} */
@NonNull
public String getTraceName(@NonNull Message message) {
+ if (message.callback instanceof TraceNameSupplier) {
+ return ((TraceNameSupplier) message.callback).getTraceName();
+ }
+
final StringBuilder sb = new StringBuilder();
sb.append(getClass().getName()).append(": ");
if (message.callback != null) {
diff --git a/core/java/android/os/TraceNameSupplier.java b/core/java/android/os/TraceNameSupplier.java
new file mode 100644
index 000000000000..e4b3a4edce38
--- /dev/null
+++ b/core/java/android/os/TraceNameSupplier.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 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.os;
+
+import android.annotation.NonNull;
+
+/**
+ * Supplier for custom trace messages.
+ *
+ * @hide
+ */
+public interface TraceNameSupplier {
+
+ /**
+ * Gets the name used for trace messages.
+ */
+ @NonNull String getTraceName();
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 5b9205d16898..351462f8d68e 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -153,6 +153,11 @@ public class StorageManager {
public static final String PROP_ISOLATED_STORAGE = "persist.sys.isolated_storage";
/** {@hide} */
public static final String PROP_ISOLATED_STORAGE_SNAPSHOT = "sys.isolated_storage_snapshot";
+ /** {@hide} */
+ public static final String PROP_FUSE = "persist.sys.fuse";
+ /** {@hide} */
+ public static final String PROP_FUSE_SNAPSHOT = "sys.fuse_snapshot";
+
/** {@hide} */
public static final String UUID_PRIVATE_INTERNAL = null;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 61d8eb13bcf2..3c26df3c560b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7631,6 +7631,19 @@ public final class Settings {
"face_unlock_always_require_confirmation";
/**
+ * Whether or not a user should re enroll their face.
+ *
+ * Face unlock re enroll.
+ * 0 = No re enrollment.
+ * 1 = Re enrollment is suggested.
+ * 2 = Re enrollment is required after a set time period.
+ * 3 = Re enrollment is required immediately.
+ *
+ * @hide
+ */
+ public static final String FACE_UNLOCK_RE_ENROLL = "face_unlock_re_enroll";
+
+ /**
* Whether or not debugging is enabled.
* @hide
*/
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 5143f1820c2d..2470d197c3fc 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -58,8 +58,8 @@ import java.util.concurrent.Executors;
* Abstract base class for the TextClassifier service.
*
* <p>A TextClassifier service provides text classification related features for the system.
- * The system's default TextClassifierService is configured in
- * {@code config_defaultTextClassifierService}. If this config has no value, a
+ * The system's default TextClassifierService provider is configured in
+ * {@code config_defaultTextClassifierPackage}. If this config has no value, a
* {@link android.view.textclassifier.TextClassifierImpl} is loaded in the calling app's process.
*
* <p>See: {@link TextClassifier}.
diff --git a/core/java/android/view/CompositionSamplingListener.java b/core/java/android/view/CompositionSamplingListener.java
index 368445cde72c..677a559cd3b0 100644
--- a/core/java/android/view/CompositionSamplingListener.java
+++ b/core/java/android/view/CompositionSamplingListener.java
@@ -28,7 +28,7 @@ import java.util.concurrent.Executor;
*/
public abstract class CompositionSamplingListener {
- private final long mNativeListener;
+ private long mNativeListener;
private final Executor mExecutor;
public CompositionSamplingListener(Executor executor) {
@@ -36,13 +36,19 @@ public abstract class CompositionSamplingListener {
mNativeListener = nativeCreate(this);
}
+ public void destroy() {
+ if (mNativeListener == 0) {
+ return;
+ }
+ unregister(this);
+ nativeDestroy(mNativeListener);
+ mNativeListener = 0;
+ }
+
@Override
protected void finalize() throws Throwable {
try {
- if (mNativeListener != 0) {
- unregister(this);
- nativeDestroy(mNativeListener);
- }
+ destroy();
} finally {
super.finalize();
}
@@ -58,6 +64,9 @@ public abstract class CompositionSamplingListener {
*/
public static void register(CompositionSamplingListener listener,
int displayId, SurfaceControl stopLayer, Rect samplingArea) {
+ if (listener.mNativeListener == 0) {
+ return;
+ }
Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
"default display only for now");
long nativeStopLayerObject = stopLayer != null ? stopLayer.mNativeObject : 0;
@@ -69,6 +78,9 @@ public abstract class CompositionSamplingListener {
* Unregisters a sampling listener.
*/
public static void unregister(CompositionSamplingListener listener) {
+ if (listener.mNativeListener == 0) {
+ return;
+ }
nativeUnregister(listener.mNativeListener);
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index db3ef20d5859..06ff568202d5 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1128,11 +1128,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
return;
}
- if (frameNumber > 0) {
- final ViewRootImpl viewRoot = getViewRootImpl();
-
- mRtTransaction.deferTransactionUntilSurface(mSurfaceControl, viewRoot.mSurface,
- frameNumber);
+ final ViewRootImpl viewRoot = getViewRootImpl();
+ if (frameNumber > 0 && viewRoot != null) {
+ if (viewRoot.mSurface.isValid()) {
+ mRtTransaction.deferTransactionUntilSurface(mSurfaceControl, viewRoot.mSurface,
+ frameNumber);
+ }
}
mRtTransaction.hide(mSurfaceControl);
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index 80de6fc65f90..562cc4ffeeaa 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -1103,6 +1103,7 @@ class SimpleMonthView extends View {
}
node.setEnabled(isDayEnabled);
+ node.setClickable(true);
if (virtualViewId == mActivatedDay) {
// TODO: This should use activated once that's supported.
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index 1ce071bd005a..5ea970d4c746 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -16,14 +16,89 @@
package com.android.internal.compat;
+import android.util.Log;
+import android.util.Slog;
import android.util.StatsLog;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
/**
* A helper class to report changes to stats log.
*
* @hide
*/
public final class ChangeReporter {
+ private static final String TAG = "CompatibilityChangeReporter";
+ private int mSource;
+
+ private final class ChangeReport {
+ int mUid;
+ long mChangeId;
+ int mState;
+
+ ChangeReport(int uid, long changeId, int state) {
+ mUid = uid;
+ mChangeId = changeId;
+ mState = state;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ChangeReport that = (ChangeReport) o;
+ return mUid == that.mUid
+ && mChangeId == that.mChangeId
+ && mState == that.mState;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUid, mChangeId, mState);
+ }
+ }
+
+ @GuardedBy("mReportedChanges")
+ private Set<ChangeReport> mReportedChanges = new HashSet<>();
+
+ public ChangeReporter(int source) {
+ mSource = source;
+ }
+
+ /**
+ * Report the change to stats log.
+ *
+ * @param uid affected by the change
+ * @param changeId the reported change id
+ * @param state of the reported change - enabled/disabled/only logged
+ */
+ public void reportChange(int uid, long changeId, int state) {
+ debugLog(uid, changeId, state);
+ ChangeReport report = new ChangeReport(uid, changeId, state);
+ synchronized (mReportedChanges) {
+ if (!mReportedChanges.contains(report)) {
+ StatsLog.write(StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId,
+ state, mSource);
+ mReportedChanges.add(report);
+ }
+ }
+ }
+
+ private void debugLog(int uid, long changeId, int state) {
+ //TODO(b/138374585): Implement rate limiting for the logs.
+ String message = String.format("Compat change id reported: %d; UID %d; state: %s", changeId,
+ uid, stateToString(state));
+ if (mSource == StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER) {
+ Slog.d(TAG, message);
+ } else {
+ Log.d(TAG, message);
+ }
+
+ }
/**
* Transforms StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE enum to a string.
@@ -43,31 +118,4 @@ public final class ChangeReporter {
return "UNKNOWN";
}
}
-
- /**
- * Constructs and returns a string to be logged to logcat when a change is reported.
- *
- * @param uid affected by the change
- * @param changeId the reported change id
- * @param state of the reported change - enabled/disabled/only logged
- * @return string to log
- */
- public static String createLogString(int uid, long changeId, int state) {
- return String.format("Compat change id reported: %d; UID %d; state: %s", changeId, uid,
- stateToString(state));
- }
-
- /**
- * Report the change to stats log.
- *
- * @param uid affected by the change
- * @param changeId the reported change id
- * @param state of the reported change - enabled/disabled/only logged
- * @param source of the logging - app process or system server
- */
- public void reportChange(int uid, long changeId, int state, int source) {
- //TODO(b/138374585): Implement rate limiting for stats log.
- StatsLog.write(StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId,
- state, source);
- }
}
diff --git a/core/java/com/android/internal/infra/ServiceConnector.java b/core/java/com/android/internal/infra/ServiceConnector.java
index d6862f0188ce..98d679eb776b 100644
--- a/core/java/com/android/internal/infra/ServiceConnector.java
+++ b/core/java/com/android/internal/infra/ServiceConnector.java
@@ -32,6 +32,7 @@ import android.text.TextUtils;
import android.util.DebugUtils;
import android.util.Log;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import java.io.PrintWriter;
@@ -351,7 +352,7 @@ public interface ServiceConnector<I extends IInterface> {
@Override
public <R> CompletionAwareJob<I, R> postForResult(@NonNull Job<I, R> job) {
CompletionAwareJob<I, R> task = new CompletionAwareJob<>();
- task.mDelegate = job;
+ task.mDelegate = Preconditions.checkNotNull(job);
enqueue(task);
return task;
}
@@ -359,7 +360,7 @@ public interface ServiceConnector<I extends IInterface> {
@Override
public <R> AndroidFuture<R> postAsync(@NonNull Job<I, CompletableFuture<R>> job) {
CompletionAwareJob<I, R> task = new CompletionAwareJob<>();
- task.mDelegate = (Job) job;
+ task.mDelegate = Preconditions.checkNotNull((Job) job);
task.mAsync = true;
enqueue(task);
return task;
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 1de2e7272f4d..d6caa0930243 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -192,6 +192,15 @@ public class RuntimeInit {
}
}
+ /**
+ * Common initialization that (unlike {@link #commonInit()} should happen prior to
+ * the Zygote fork.
+ */
+ public static void preForkInit() {
+ if (DEBUG) Slog.d(TAG, "Entered preForkInit.");
+ RuntimeInit.enableDdms();
+ }
+
@UnsupportedAppUsage
protected static final void commonInit() {
if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
@@ -324,7 +333,7 @@ public class RuntimeInit {
@UnsupportedAppUsage
public static final void main(String[] argv) {
- enableDdms();
+ preForkInit();
if (argv.length == 2 && argv[1].equals("application")) {
if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application");
redirectLogStreams();
@@ -418,7 +427,7 @@ public class RuntimeInit {
/**
* Enable DDMS.
*/
- static final void enableDdms() {
+ private static void enableDdms() {
// Register handlers for DDM messages.
android.ddm.DdmRegister.registerHandlers();
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 3be1a1aefe57..158700b2a449 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -847,7 +847,7 @@ public class ZygoteInit {
TimingsTraceLog bootTimingsTraceLog = new TimingsTraceLog(bootTimeTag,
Trace.TRACE_TAG_DALVIK);
bootTimingsTraceLog.traceBegin("ZygoteInit");
- RuntimeInit.enableDdms();
+ RuntimeInit.preForkInit();
boolean startSystemServer = false;
String zygoteSocketName = "zygote";
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
index 1fdb1f30125f..e12c031f0036 100755
--- a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
@@ -16,6 +16,7 @@
package com.android.internal.util.function.pooled;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Message;
import android.text.TextUtils;
@@ -527,6 +528,36 @@ final class PooledLambdaImpl<R> extends OmniFunction<Object,
return r;
}
+ // TODO: add unit test
+ @NonNull
+ private static String getFriendlyName(@NonNull Object function) {
+ // Full function has one of the following formats:
+ // package-$$Lambda$class$randomId
+ // package-$$Lambda$randomId
+ //
+ // We just want just package.class$Lambda (or package$Lambda) respectively
+
+ final String fullFunction = function.toString();
+
+ final int endPkgIdx = fullFunction.indexOf("-$$");
+ if (endPkgIdx == -1) return fullFunction;
+
+ // firstDollarIdx could be either beginning of class or beginning of the random id
+ final int firstDollarIdx = fullFunction.indexOf('$', endPkgIdx + 3);
+ if (firstDollarIdx == -1) return fullFunction;
+
+ final int endClassIdx = fullFunction.indexOf('$', firstDollarIdx + 1);
+ if (endClassIdx == -1) {
+ // Just package
+ return fullFunction.substring(0, endPkgIdx - 1) + "$Lambda";
+ }
+
+ // Package + class
+ return fullFunction.substring(0, endPkgIdx)
+ + fullFunction.substring(firstDollarIdx + 1, endClassIdx)
+ + "$Lambda";
+ }
+
private static void setIfInBounds(Object[] array, int i, Object a) {
if (i < ArrayUtils.size(array)) array[i] = a;
}
@@ -566,6 +597,11 @@ final class PooledLambdaImpl<R> extends OmniFunction<Object,
return this;
}
+ @Override
+ public String getTraceName() {
+ return getFriendlyName(mFunc);
+ }
+
private boolean isRecycled() {
return (mFlags & FLAG_RECYCLED) != 0;
}
diff --git a/core/java/com/android/internal/util/function/pooled/PooledRunnable.java b/core/java/com/android/internal/util/function/pooled/PooledRunnable.java
index 89ca82e2f3ce..f0bc2cadb1c5 100644
--- a/core/java/com/android/internal/util/function/pooled/PooledRunnable.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledRunnable.java
@@ -16,6 +16,8 @@
package com.android.internal.util.function.pooled;
+import android.os.TraceNameSupplier;
+
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
/**
@@ -24,7 +26,8 @@ import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
* @see PooledLambda
* @hide
*/
-public interface PooledRunnable extends PooledLambda, Runnable, ThrowingRunnable {
+public interface PooledRunnable
+ extends PooledLambda, Runnable, ThrowingRunnable, TraceNameSupplier {
/** @inheritDoc */
PooledRunnable recycleOnUse();
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 5a0f16e589ce..e3b8560ae749 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -54,6 +54,8 @@ cc_library_shared {
whole_static_libs: ["libandroid_graphics"],
+ export_static_lib_headers: ["libandroid_graphics"],
+
shared_libs: [
"libbase",
"libcutils",
@@ -337,7 +339,9 @@ cc_library_static {
cppflags: ["-Wno-conversion-null"],
srcs: [
+ "android/graphics/apex/android_bitmap.cpp",
"android/graphics/apex/android_region.cpp",
+ "android/graphics/apex/android_paint.cpp",
"android_graphics_Canvas.cpp",
"android_graphics_ColorSpace.cpp",
@@ -390,7 +394,6 @@ cc_library_static {
],
export_include_dirs: [
- ".",
"android/graphics/apex/include",
],
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 18a1b43d3f5f..89c12f88594d 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -265,6 +265,20 @@ void imageInfo(JNIEnv* env, jobject bitmap, AndroidBitmapInfo* info) {
info->format = ANDROID_BITMAP_FORMAT_NONE;
break;
}
+ switch (imageInfo.alphaType()) {
+ case kUnknown_SkAlphaType:
+ LOG_ALWAYS_FATAL("Bitmap has no alpha type");
+ break;
+ case kOpaque_SkAlphaType:
+ info->flags |= ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE;
+ break;
+ case kPremul_SkAlphaType:
+ info->flags |= ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
+ break;
+ case kUnpremul_SkAlphaType:
+ info->flags |= ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL;
+ break;
+ }
}
void* lockPixels(JNIEnv* env, jobject bitmap) {
diff --git a/core/jni/android/graphics/Bitmap.h b/core/jni/android/graphics/Bitmap.h
index 06e31a1518ca..59adbb207a0c 100644
--- a/core/jni/android/graphics/Bitmap.h
+++ b/core/jni/android/graphics/Bitmap.h
@@ -39,8 +39,6 @@ jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
jobject ninePatchInsets = nullptr, int density = -1);
-void toSkBitmap(jlong bitmapHandle, SkBitmap* outBitmap);
-
Bitmap& toBitmap(JNIEnv* env, jobject bitmap);
Bitmap& toBitmap(jlong bitmapHandle);
diff --git a/core/jni/android/graphics/apex/TypeCast.h b/core/jni/android/graphics/apex/TypeCast.h
new file mode 100644
index 000000000000..96721d007951
--- /dev/null
+++ b/core/jni/android/graphics/apex/TypeCast.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_GRAPHICS_TYPECAST_H
+#define ANDROID_GRAPHICS_TYPECAST_H
+
+struct ABitmap;
+struct ACanvas;
+struct APaint;
+
+namespace android {
+
+ class Bitmap;
+ class Canvas;
+ class Paint;
+
+ class TypeCast {
+ public:
+ static inline Bitmap& toBitmapRef(const ABitmap* bitmap) {
+ return const_cast<Bitmap&>(reinterpret_cast<const Bitmap&>(*bitmap));
+ }
+
+ static inline Bitmap* toBitmap(ABitmap* bitmap) {
+ return reinterpret_cast<Bitmap*>(bitmap);
+ }
+
+ static inline ABitmap* toABitmap(Bitmap* bitmap) {
+ return reinterpret_cast<ABitmap*>(bitmap);
+ }
+
+ static inline Canvas* toCanvas(ACanvas* canvas) {
+ return reinterpret_cast<Canvas*>(canvas);
+ }
+
+ static inline ACanvas* toACanvas(Canvas* canvas) {
+ return reinterpret_cast<ACanvas *>(canvas);
+ }
+
+ static inline const Paint& toPaintRef(const APaint* paint) {
+ return reinterpret_cast<const Paint&>(*paint);
+ }
+
+ static inline const Paint* toPaint(const APaint* paint) {
+ return reinterpret_cast<const Paint*>(paint);
+ }
+
+ static inline Paint* toPaint(APaint* paint) {
+ return reinterpret_cast<Paint*>(paint);
+ }
+
+ static inline APaint* toAPaint(Paint* paint) {
+ return reinterpret_cast<APaint*>(paint);
+ }
+ };
+}; // namespace android
+
+#endif // ANDROID_GRAPHICS_TYPECAST_H
diff --git a/core/jni/android/graphics/apex/android_bitmap.cpp b/core/jni/android/graphics/apex/android_bitmap.cpp
new file mode 100644
index 000000000000..96cc5db8a5a4
--- /dev/null
+++ b/core/jni/android/graphics/apex/android_bitmap.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "android/graphics/bitmap.h"
+#include "Bitmap.h"
+#include "TypeCast.h"
+
+#include <hwui/Bitmap.h>
+
+using namespace android;
+
+ABitmap* ABitmap_acquireBitmapFromJava(JNIEnv* env, jobject bitmapObj) {
+ Bitmap& bitmap = android::bitmap::toBitmap(env, bitmapObj);
+ bitmap.ref();
+ return TypeCast::toABitmap(&bitmap);
+}
+
+void ABitmap_acquireRef(ABitmap* bitmap) {
+ SkSafeRef(TypeCast::toBitmap(bitmap));
+}
+
+void ABitmap_releaseRef(ABitmap* bitmap) {
+ SkSafeUnref(TypeCast::toBitmap(bitmap));
+}
+
+static AndroidBitmapFormat getFormat(Bitmap* bitmap) {
+ switch (bitmap->colorType()) {
+ case kN32_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_8888;
+ case kRGB_565_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGB_565;
+ case kARGB_4444_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_4444;
+ case kAlpha_8_SkColorType:
+ return ANDROID_BITMAP_FORMAT_A_8;
+ case kRGBA_F16_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_F16;
+ default:
+ return ANDROID_BITMAP_FORMAT_NONE;
+ }
+}
+
+static SkColorType getColorType(AndroidBitmapFormat format) {
+ switch (format) {
+ case ANDROID_BITMAP_FORMAT_RGBA_8888:
+ return kN32_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGB_565:
+ return kRGB_565_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGBA_4444:
+ return kARGB_4444_SkColorType;
+ case ANDROID_BITMAP_FORMAT_A_8:
+ return kAlpha_8_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGBA_F16:
+ return kRGBA_F16_SkColorType;
+ default:
+ return kUnknown_SkColorType;
+ }
+}
+
+ABitmap* ABitmap_copy(ABitmap* srcBitmapHandle, AndroidBitmapFormat dstFormat) {
+ SkColorType dstColorType = getColorType(dstFormat);
+ if (srcBitmapHandle && dstColorType != kUnknown_SkColorType) {
+ SkBitmap srcBitmap;
+ TypeCast::toBitmap(srcBitmapHandle)->getSkBitmap(&srcBitmap);
+
+ sk_sp<Bitmap> dstBitmap =
+ Bitmap::allocateHeapBitmap(srcBitmap.info().makeColorType(dstColorType));
+ if (dstBitmap && srcBitmap.readPixels(dstBitmap->info(), dstBitmap->pixels(),
+ dstBitmap->rowBytes(), 0, 0)) {
+ return TypeCast::toABitmap(dstBitmap.release());
+ }
+ }
+ return nullptr;
+}
+
+AndroidBitmapInfo ABitmap_getInfo(ABitmap* bitmapHandle) {
+ Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle);
+
+ AndroidBitmapInfo info;
+ info.width = bitmap->width();
+ info.height = bitmap->height();
+ info.stride = bitmap->rowBytes();
+ info.format = getFormat(bitmap);
+ return info;
+}
+
+void* ABitmap_getPixels(ABitmap* bitmapHandle) {
+ Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle);
+ if (bitmap->isHardware()) {
+ return nullptr;
+ }
+ return bitmap->pixels();
+}
diff --git a/core/jni/android/graphics/apex/android_canvas.cpp b/core/jni/android/graphics/apex/android_canvas.cpp
index 7a4495f4f259..527a745426e3 100644
--- a/core/jni/android/graphics/apex/android_canvas.cpp
+++ b/core/jni/android/graphics/apex/android_canvas.cpp
@@ -16,6 +16,7 @@
#include "android/graphics/canvas.h"
+#include "TypeCast.h"
#include "GraphicsJNI.h"
#include <hwui/Canvas.h>
@@ -25,14 +26,6 @@
using namespace android;
-static inline Canvas* toCanvas(ACanvas* aCanvas) {
- return reinterpret_cast<Canvas*>(aCanvas);
-}
-
-static inline ACanvas* toACanvas(Canvas* canvas) {
- return reinterpret_cast<ACanvas*>(canvas);
-}
-
bool ACanvas_isSupportedPixelFormat(int32_t bufferFormat) {
ANativeWindow_Buffer buffer { 0, 0, 0, bufferFormat, nullptr, {0} };
const SkColorType colorType = uirenderer::ANativeWindowToImageInfo(buffer, nullptr).colorType();
@@ -40,11 +33,11 @@ bool ACanvas_isSupportedPixelFormat(int32_t bufferFormat) {
}
ACanvas* ACanvas_getNativeHandleFromJava(JNIEnv* env, jobject canvasObj) {
- return toACanvas(GraphicsJNI::getNativeCanvas(env, canvasObj));
+ return TypeCast::toACanvas(GraphicsJNI::getNativeCanvas(env, canvasObj));
}
-void ACanvas_setBuffer(ACanvas* canvas, const ANativeWindow_Buffer* buffer,
- int32_t /*android_dataspace_t*/ dataspace) {
+static SkBitmap convert(const ANativeWindow_Buffer* buffer,
+ int32_t /*android_dataspace_t*/ dataspace) {
SkBitmap bitmap;
if (buffer != nullptr && buffer->width > 0 && buffer->height > 0) {
sk_sp<SkColorSpace> cs(uirenderer::DataSpaceToColorSpace((android_dataspace)dataspace));
@@ -53,18 +46,44 @@ void ACanvas_setBuffer(ACanvas* canvas, const ANativeWindow_Buffer* buffer,
bitmap.setInfo(imageInfo, rowBytes);
bitmap.setPixels(buffer->bits);
}
+ return bitmap;
+}
- toCanvas(canvas)->setBitmap(bitmap);
+ACanvas* ACanvas_createCanvas(const ANativeWindow_Buffer* buffer,
+ int32_t /*android_dataspace_t*/ dataspace) {
+ return TypeCast::toACanvas(Canvas::create_canvas(convert(buffer, dataspace)));
}
-void ACanvas_clipRect(ACanvas* canvas, const ARect& clipRect, bool /*doAA*/) {
+void ACanvas_destroyCanvas(ACanvas* canvas) {
+ delete TypeCast::toCanvas(canvas);
+}
+
+void ACanvas_setBuffer(ACanvas* canvas, const ANativeWindow_Buffer* buffer,
+ int32_t /*android_dataspace_t*/ dataspace) {
+
+
+ TypeCast::toCanvas(canvas)->setBitmap(convert(buffer, dataspace));
+}
+
+void ACanvas_clipRect(ACanvas* canvas, const ARect* clipRect, bool /*doAA*/) {
//TODO update Canvas to take antialias param
- toCanvas(canvas)->clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
- SkClipOp::kIntersect);
+ TypeCast::toCanvas(canvas)->clipRect(clipRect->left, clipRect->top, clipRect->right,
+ clipRect->bottom, SkClipOp::kIntersect);
}
-void ACanvas_clipOutRect(ACanvas* canvas, const ARect& clipRect, bool /*doAA*/) {
+void ACanvas_clipOutRect(ACanvas* canvas, const ARect* clipRect, bool /*doAA*/) {
//TODO update Canvas to take antialias param
- toCanvas(canvas)->clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
- SkClipOp::kDifference);
+ TypeCast::toCanvas(canvas)->clipRect(clipRect->left, clipRect->top, clipRect->right,
+ clipRect->bottom, SkClipOp::kDifference);
+}
+
+void ACanvas_drawRect(ACanvas* canvas, const ARect* rect, const APaint* paint) {
+ TypeCast::toCanvas(canvas)->drawRect(rect->left, rect->top, rect->right, rect->bottom,
+ TypeCast::toPaintRef(paint));
+}
+
+void ACanvas_drawBitmap(ACanvas* canvas, const ABitmap* bitmap, float left, float top,
+ const APaint* paint) {
+ TypeCast::toCanvas(canvas)->drawBitmap(TypeCast::toBitmapRef(bitmap), left, top,
+ TypeCast::toPaint(paint));
}
diff --git a/core/jni/android/graphics/apex/android_paint.cpp b/core/jni/android/graphics/apex/android_paint.cpp
new file mode 100644
index 000000000000..70bd085343ce
--- /dev/null
+++ b/core/jni/android/graphics/apex/android_paint.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "android/graphics/paint.h"
+
+#include "TypeCast.h"
+
+#include <hwui/Paint.h>
+
+using namespace android;
+
+
+APaint* APaint_createPaint() {
+ return TypeCast::toAPaint(new Paint());
+}
+
+void APaint_destroyPaint(APaint* paint) {
+ delete TypeCast::toPaint(paint);
+}
+
+static SkBlendMode convertBlendMode(ABlendMode blendMode) {
+ switch (blendMode) {
+ case ABLEND_MODE_CLEAR:
+ return SkBlendMode::kClear;
+ case ABLEND_MODE_SRC_OVER:
+ return SkBlendMode::kSrcOver;
+ case ABLEND_MODE_SRC:
+ return SkBlendMode::kSrc;
+ }
+}
+
+void APaint_setBlendMode(APaint* paint, ABlendMode blendMode) {
+ TypeCast::toPaint(paint)->setBlendMode(convertBlendMode(blendMode));
+}
diff --git a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h
new file mode 100644
index 000000000000..bfa4c8df407f
--- /dev/null
+++ b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANDROID_GRAPHICS_BITMAP_H
+#define ANDROID_GRAPHICS_BITMAP_H
+
+#include <android/bitmap.h>
+#include <jni.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/**
+ * Opaque handle for a native graphics bitmap.
+ */
+typedef struct ABitmap ABitmap;
+
+ABitmap* ABitmap_acquireBitmapFromJava(JNIEnv* env, jobject bitmapObj);
+
+ABitmap* ABitmap_copy(ABitmap* srcBitmap, AndroidBitmapFormat dstFormat);
+
+void ABitmap_acquireRef(ABitmap* bitmap);
+void ABitmap_releaseRef(ABitmap* bitmap);
+
+AndroidBitmapInfo ABitmap_getInfo(ABitmap* bitmap);
+
+void* ABitmap_getPixels(ABitmap* bitmap);
+
+__END_DECLS
+
+#ifdef __cplusplus
+namespace android {
+namespace graphics {
+ class Bitmap {
+ public:
+ Bitmap() : mBitmap(nullptr) {}
+ Bitmap(JNIEnv* env, jobject bitmapObj) :
+ mBitmap(ABitmap_acquireBitmapFromJava(env, bitmapObj)) {}
+ Bitmap(const Bitmap& src) : mBitmap(src.mBitmap) { ABitmap_acquireRef(src.mBitmap); }
+ ~Bitmap() { ABitmap_releaseRef(mBitmap); }
+
+ // copy operator
+ Bitmap& operator=(const Bitmap& other) {
+ if (&other != this) {
+ ABitmap_releaseRef(mBitmap);
+ mBitmap = other.mBitmap;
+ ABitmap_acquireRef(mBitmap);
+ }
+ return *this;
+ }
+
+ // move operator
+ Bitmap& operator=(Bitmap&& other) {
+ if (&other != this) {
+ ABitmap_releaseRef(mBitmap);
+ mBitmap = other.mBitmap;
+ other.mBitmap = nullptr;
+ }
+ return *this;
+ }
+
+ Bitmap copy(AndroidBitmapFormat dstFormat) const {
+ return Bitmap(ABitmap_copy(mBitmap, dstFormat));
+ }
+
+ bool isValid() const { return mBitmap != nullptr; }
+ bool isEmpty() const {
+ AndroidBitmapInfo info = getInfo();
+ return info.width <= 0 || info.height <= 0;
+ }
+ void reset() {
+ ABitmap_releaseRef(mBitmap);
+ mBitmap = nullptr;
+ }
+
+ const ABitmap* get() const { return mBitmap; }
+
+ AndroidBitmapInfo getInfo() const { return ABitmap_getInfo(mBitmap); }
+ void* getPixels() const { return ABitmap_getPixels(mBitmap); }
+ private:
+ // takes ownership of the provided ABitmap
+ Bitmap(ABitmap* bitmap) : mBitmap(bitmap) {}
+
+ ABitmap* mBitmap;
+ };
+}; // namespace graphics
+}; // namespace android
+#endif // __cplusplus
+
+#endif // ANDROID_GRAPHICS_BITMAP_H \ No newline at end of file
diff --git a/core/jni/android/graphics/apex/include/android/graphics/canvas.h b/core/jni/android/graphics/apex/include/android/graphics/canvas.h
index c35a7d69b836..190aba4565f8 100644
--- a/core/jni/android/graphics/apex/include/android/graphics/canvas.h
+++ b/core/jni/android/graphics/apex/include/android/graphics/canvas.h
@@ -16,6 +16,8 @@
#ifndef ANDROID_GRAPHICS_CANVAS_H
#define ANDROID_GRAPHICS_CANVAS_H
+#include <android/graphics/bitmap.h>
+#include <android/graphics/paint.h>
#include <android/native_window.h>
#include <android/rect.h>
#include <jni.h>
@@ -23,8 +25,8 @@
__BEGIN_DECLS
/**
-* Opaque handle for a native graphics canvas.
-*/
+ * Opaque handle for a native graphics canvas.
+ */
typedef struct ACanvas ACanvas;
// One of AHardwareBuffer_Format.
@@ -33,34 +35,104 @@ bool ACanvas_isSupportedPixelFormat(int32_t bufferFormat);
/**
* Returns a native handle to a Java android.graphics.Canvas
*
- * @param env
- * @param canvas
* @return ACanvas* that is only valid for the life of the jobject.
*/
ACanvas* ACanvas_getNativeHandleFromJava(JNIEnv* env, jobject canvas);
/**
+ * Creates a canvas that wraps the buffer
+ *
+ * @param buffer required
+ */
+ACanvas* ACanvas_createCanvas(const ANativeWindow_Buffer* buffer,
+ int32_t /*android_dataspace_t*/ dataspace);
+
+void ACanvas_destroyCanvas(ACanvas* canvas);
+
+/**
* Updates the canvas to render into the pixels in the provided buffer
*
- * @param canvas
* @param buffer The buffer that will provide the backing store for this canvas. The buffer must
* remain valid until the this method is called again with either another active
* buffer or nullptr. If nullptr is given the canvas will release the previous buffer
* and set an empty backing store.
- * @param dataspace
*/
void ACanvas_setBuffer(ACanvas* canvas, const ANativeWindow_Buffer* buffer,
int32_t /*android_dataspace_t*/ dataspace);
/**
* Clips operations on the canvas to the intersection of the current clip and the provided clipRect.
+ *
+ * @param clipRect required
*/
-void ACanvas_clipRect(ACanvas* canvas, const ARect& clipRect, bool doAntiAlias = false);
+void ACanvas_clipRect(ACanvas* canvas, const ARect* clipRect, bool doAntiAlias = false);
/**
* Clips operations on the canvas to the difference of the current clip and the provided clipRect.
+ *
+ * @param clipRect required
+ */
+void ACanvas_clipOutRect(ACanvas* canvas, const ARect* clipRect, bool doAntiAlias = false);
+
+/**
+ *
+ * @param rect required
+ * @param paint required
+ */
+void ACanvas_drawRect(ACanvas* canvas, const ARect* rect, const APaint* paint);
+
+/**
+ *
+ * @param bitmap required
+ * @param left
+ * @param top
+ * @param paint
*/
-void ACanvas_clipOutRect(ACanvas* canvas, const ARect& clipRect, bool doAntiAlias = false);
+void ACanvas_drawBitmap(ACanvas* canvas, const ABitmap* bitmap, float left, float top,
+ const APaint* paint);
__END_DECLS
+
+#ifdef __cplusplus
+namespace android {
+namespace graphics {
+ class Canvas {
+ public:
+ Canvas(JNIEnv* env, jobject canvasObj) :
+ mCanvas(ACanvas_getNativeHandleFromJava(env, canvasObj)),
+ mOwnedPtr(false) {}
+ Canvas(const ANativeWindow_Buffer& buffer, int32_t /*android_dataspace_t*/ dataspace) :
+ mCanvas(ACanvas_createCanvas(&buffer, dataspace)),
+ mOwnedPtr(true) {}
+ ~Canvas() {
+ if (mOwnedPtr) {
+ ACanvas_destroyCanvas(mCanvas);
+ }
+ }
+
+ void setBuffer(const ANativeWindow_Buffer* buffer,
+ int32_t /*android_dataspace_t*/ dataspace) {
+ ACanvas_setBuffer(mCanvas, buffer, dataspace);
+ }
+
+ void clipRect(const ARect& clipRect, bool doAntiAlias = false) {
+ ACanvas_clipRect(mCanvas, &clipRect, doAntiAlias);
+ }
+
+ void drawRect(const ARect& rect, const Paint& paint) {
+ ACanvas_drawRect(mCanvas, &rect, &paint.get());
+ }
+ void drawBitmap(const Bitmap& bitmap, float left, float top, const Paint* paint) {
+ const APaint* aPaint = (paint) ? &paint->get() : nullptr;
+ ACanvas_drawBitmap(mCanvas, bitmap.get(), left, top, aPaint);
+ }
+
+ private:
+ ACanvas* mCanvas;
+ const bool mOwnedPtr;
+ };
+}; // namespace graphics
+}; // namespace android
+#endif // __cplusplus
+
#endif // ANDROID_GRAPHICS_CANVAS_H \ No newline at end of file
diff --git a/core/jni/android/graphics/apex/include/android/graphics/paint.h b/core/jni/android/graphics/apex/include/android/graphics/paint.h
new file mode 100644
index 000000000000..5895e006bf93
--- /dev/null
+++ b/core/jni/android/graphics/apex/include/android/graphics/paint.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANDROID_GRAPHICS_PAINT_H
+#define ANDROID_GRAPHICS_PAINT_H
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/**
+ * Opaque handle for a native graphics canvas.
+ */
+typedef struct APaint APaint;
+
+/** Bitmap pixel format. */
+enum ABlendMode {
+ /** replaces destination with zero: fully transparent */
+ ABLEND_MODE_CLEAR = 0,
+ /** source over destination */
+ ABLEND_MODE_SRC_OVER = 1,
+ /** replaces destination **/
+ ABLEND_MODE_SRC = 2,
+};
+
+APaint* APaint_createPaint();
+
+void APaint_destroyPaint(APaint* paint);
+
+void APaint_setBlendMode(APaint* paint, ABlendMode blendMode);
+
+__END_DECLS
+
+#ifdef __cplusplus
+namespace android {
+namespace graphics {
+ class Paint {
+ public:
+ Paint() : mPaint(APaint_createPaint()) {}
+ ~Paint() { APaint_destroyPaint(mPaint); }
+
+ void setBlendMode(ABlendMode blendMode) { APaint_setBlendMode(mPaint, blendMode); }
+
+ const APaint& get() const { return *mPaint; }
+
+ private:
+ APaint* mPaint;
+ };
+}; // namespace graphics
+}; // namespace android
+#endif // __cplusplus
+
+
+#endif // ANDROID_GRAPHICS_PAINT_H \ No newline at end of file
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index 58c5871aba28..82601baee914 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -24,15 +24,13 @@
#include <assert.h>
#include <dlfcn.h>
+#include <android/graphics/bitmap.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES3/gl3.h>
#include <ETC1/etc1.h>
-#include <SkBitmap.h>
-
#include "core_jni_helpers.h"
-#include "android/graphics/Bitmap.h"
#undef LOG_TAG
#define LOG_TAG "OpenGLUtil"
@@ -628,31 +626,27 @@ void util_multiplyMV(JNIEnv *env, jclass clazz,
// The internal format is no longer the same as pixel format, per Table 2 in
// https://www.khronos.org/registry/OpenGL-Refpages/es3.1/html/glTexImage2D.xhtml
-static int checkInternalFormat(SkColorType colorType, int internalformat,
- int type)
+static bool checkInternalFormat(int32_t bitmapFormat, int internalformat, int type)
{
- switch(colorType) {
- case kN32_SkColorType:
- return (type == GL_UNSIGNED_BYTE &&
- internalformat == GL_RGBA) ||
- (type == GL_UNSIGNED_BYTE &&
- internalformat == GL_SRGB8_ALPHA8) ? 0 : -1;
- case kAlpha_8_SkColorType:
- return (type == GL_UNSIGNED_BYTE &&
- internalformat == GL_ALPHA) ? 0 : -1;
- case kARGB_4444_SkColorType:
- return (type == GL_UNSIGNED_SHORT_4_4_4_4 &&
- internalformat == GL_RGBA) ? 0 : -1;
- case kRGB_565_SkColorType:
- return (type == GL_UNSIGNED_SHORT_5_6_5 &&
- internalformat == GL_RGB) ? 0 : -1;
- case kRGBA_F16_SkColorType:
- return (type == GL_HALF_FLOAT &&
- internalformat == GL_RGBA16F) ? 0 : -1;
+ if (internalformat == GL_PALETTE8_RGBA8_OES) {
+ return false;
+ }
+ switch(bitmapFormat) {
+ case ANDROID_BITMAP_FORMAT_RGBA_8888:
+ return (type == GL_UNSIGNED_BYTE && internalformat == GL_RGBA) ||
+ (type == GL_UNSIGNED_BYTE && internalformat == GL_SRGB8_ALPHA8);
+ case ANDROID_BITMAP_FORMAT_A_8:
+ return (type == GL_UNSIGNED_BYTE && internalformat == GL_ALPHA);
+ case ANDROID_BITMAP_FORMAT_RGBA_4444:
+ return (type == GL_UNSIGNED_SHORT_4_4_4_4 && internalformat == GL_RGBA);
+ case ANDROID_BITMAP_FORMAT_RGB_565:
+ return (type == GL_UNSIGNED_SHORT_5_6_5 && internalformat == GL_RGB);
+ case ANDROID_BITMAP_FORMAT_RGBA_F16:
+ return (type == GL_HALF_FLOAT && internalformat == GL_RGBA16F);
default:
break;
}
- return -1;
+ return false;
}
// The internal format is no longer the same as pixel format, per Table 2 in
@@ -670,107 +664,92 @@ static int getPixelFormatFromInternalFormat(uint32_t internalFormat) {
}
}
-static int getInternalFormat(SkColorType colorType)
-{
- switch(colorType) {
- case kAlpha_8_SkColorType:
+static int getInternalFormat(int32_t bitmapFormat) {
+ switch(bitmapFormat) {
+ case ANDROID_BITMAP_FORMAT_A_8:
return GL_ALPHA;
- case kARGB_4444_SkColorType:
+ case ANDROID_BITMAP_FORMAT_RGBA_4444:
return GL_RGBA;
- case kN32_SkColorType:
+ case ANDROID_BITMAP_FORMAT_RGBA_8888:
return GL_RGBA;
- case kRGB_565_SkColorType:
+ case ANDROID_BITMAP_FORMAT_RGB_565:
return GL_RGB;
- case kRGBA_F16_SkColorType:
+ case ANDROID_BITMAP_FORMAT_RGBA_F16:
return GL_RGBA16F;
default:
return -1;
}
}
-static int getType(SkColorType colorType)
-{
- switch(colorType) {
- case kAlpha_8_SkColorType:
+static int getType(int32_t bitmapFormat) {
+ switch(bitmapFormat) {
+ case ANDROID_BITMAP_FORMAT_A_8:
return GL_UNSIGNED_BYTE;
- case kARGB_4444_SkColorType:
+ case ANDROID_BITMAP_FORMAT_RGBA_4444:
return GL_UNSIGNED_SHORT_4_4_4_4;
- case kN32_SkColorType:
+ case ANDROID_BITMAP_FORMAT_RGBA_8888:
return GL_UNSIGNED_BYTE;
- case kRGB_565_SkColorType:
+ case ANDROID_BITMAP_FORMAT_RGB_565:
return GL_UNSIGNED_SHORT_5_6_5;
- case kRGBA_F16_SkColorType:
+ case ANDROID_BITMAP_FORMAT_RGBA_F16:
return GL_HALF_FLOAT;
default:
return -1;
}
}
-static jint util_getInternalFormat(JNIEnv *env, jclass clazz,
- jlong bitmapPtr)
+static jint util_getInternalFormat(JNIEnv *env, jclass clazz, jobject bitmapObj)
{
- SkBitmap nativeBitmap;
- bitmap::toSkBitmap(bitmapPtr, &nativeBitmap);
- return getInternalFormat(nativeBitmap.colorType());
+ graphics::Bitmap bitmap(env, bitmapObj);
+ return getInternalFormat(bitmap.getInfo().format);
}
-static jint util_getType(JNIEnv *env, jclass clazz,
- jlong bitmapPtr)
+static jint util_getType(JNIEnv *env, jclass clazz, jobject bitmapObj)
{
- SkBitmap nativeBitmap;
- bitmap::toSkBitmap(bitmapPtr, &nativeBitmap);
- return getType(nativeBitmap.colorType());
+ graphics::Bitmap bitmap(env, bitmapObj);
+ return getType(bitmap.getInfo().format);
}
-static jint util_texImage2D(JNIEnv *env, jclass clazz,
- jint target, jint level, jint internalformat,
- jlong bitmapPtr, jint type, jint border)
+static jint util_texImage2D(JNIEnv *env, jclass clazz, jint target, jint level,
+ jint internalformat, jobject bitmapObj, jint type, jint border)
{
- SkBitmap bitmap;
- bitmap::toSkBitmap(bitmapPtr, &bitmap);
- SkColorType colorType = bitmap.colorType();
+ graphics::Bitmap bitmap(env, bitmapObj);
+ AndroidBitmapInfo bitmapInfo = bitmap.getInfo();
+
if (internalformat < 0) {
- internalformat = getInternalFormat(colorType);
+ internalformat = getInternalFormat(bitmapInfo.format);
}
if (type < 0) {
- type = getType(colorType);
- }
- int err = checkInternalFormat(colorType, internalformat, type);
- if (err)
- return err;
- const int w = bitmap.width();
- const int h = bitmap.height();
- const void* p = bitmap.getPixels();
- if (internalformat == GL_PALETTE8_RGBA8_OES) {
- err = -1;
- } else {
- glTexImage2D(target, level, internalformat, w, h, border,
- getPixelFormatFromInternalFormat(internalformat), type, p);
+ type = getType(bitmapInfo.format);
}
- return err;
+
+ if (checkInternalFormat(bitmapInfo.format, internalformat, type)) {
+ glTexImage2D(target, level, internalformat, bitmapInfo.width, bitmapInfo.height, border,
+ getPixelFormatFromInternalFormat(internalformat), type, bitmap.getPixels());
+ return 0;
+ }
+ return -1;
}
-static jint util_texSubImage2D(JNIEnv *env, jclass clazz,
- jint target, jint level, jint xoffset, jint yoffset,
- jlong bitmapPtr, jint format, jint type)
+static jint util_texSubImage2D(JNIEnv *env, jclass clazz, jint target, jint level,
+ jint xoffset, jint yoffset, jobject bitmapObj, jint format, jint type)
{
- SkBitmap bitmap;
- bitmap::toSkBitmap(bitmapPtr, &bitmap);
- SkColorType colorType = bitmap.colorType();
- int internalFormat = getInternalFormat(colorType);
+ graphics::Bitmap bitmap(env, bitmapObj);
+ AndroidBitmapInfo bitmapInfo = bitmap.getInfo();
+
+ int internalFormat = getInternalFormat(bitmapInfo.format);
if (format < 0) {
format = getPixelFormatFromInternalFormat(internalFormat);
if (format == GL_PALETTE8_RGBA8_OES)
return -1; // glCompressedTexSubImage2D() not supported
}
- int err = checkInternalFormat(colorType, internalFormat, type);
- if (err)
- return err;
- const int w = bitmap.width();
- const int h = bitmap.height();
- const void* p = bitmap.getPixels();
- glTexSubImage2D(target, level, xoffset, yoffset, w, h, format, type, p);
- return 0;
+
+ if (checkInternalFormat(bitmapInfo.format, internalFormat, type)) {
+ glTexSubImage2D(target, level, xoffset, yoffset, bitmapInfo.width, bitmapInfo.height,
+ format, type, bitmap.getPixels());
+ return 0;
+ }
+ return -1;
}
/*
@@ -1036,10 +1015,10 @@ static const JNINativeMethod gVisibilityMethods[] = {
};
static const JNINativeMethod gUtilsMethods[] = {
- { "native_getInternalFormat", "(J)I", (void*) util_getInternalFormat },
- { "native_getType", "(J)I", (void*) util_getType },
- { "native_texImage2D", "(IIIJII)I", (void*)util_texImage2D },
- { "native_texSubImage2D", "(IIIIJII)I", (void*)util_texSubImage2D },
+ { "native_getInternalFormat", "(Landroid/graphics/Bitmap;)I", (void*) util_getInternalFormat },
+ { "native_getType", "(Landroid/graphics/Bitmap;)I", (void*) util_getType },
+ { "native_texImage2D", "(IIILandroid/graphics/Bitmap;II)I", (void*)util_texImage2D },
+ { "native_texSubImage2D", "(IIIILandroid/graphics/Bitmap;II)I", (void*)util_texSubImage2D },
};
static const JNINativeMethod gEtc1Methods[] = {
diff --git a/core/jni/android_graphics_GraphicBuffer.cpp b/core/jni/android_graphics_GraphicBuffer.cpp
index 43d22eb7df0e..b6d50898a057 100644
--- a/core/jni/android_graphics_GraphicBuffer.cpp
+++ b/core/jni/android_graphics_GraphicBuffer.cpp
@@ -178,9 +178,9 @@ static jboolean android_graphics_GraphicBuffer_lockCanvas(JNIEnv* env, jobject,
nativeBuffer.format = AHardwareBuffer_convertFromPixelFormat(buffer->getPixelFormat());
nativeBuffer.bits = bits;
- ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj);
- ACanvas_setBuffer(canvas, &nativeBuffer, ADATASPACE_UNKNOWN);
- ACanvas_clipRect(canvas, {rect.left, rect.top, rect.right, rect.bottom});
+ graphics::Canvas canvas(env, canvasObj);
+ canvas.setBuffer(&nativeBuffer, ADATASPACE_UNKNOWN);
+ canvas.clipRect({rect.left, rect.top, rect.right, rect.bottom});
if (dirtyRect) {
INVOKEV(dirtyRect, gRectClassInfo.set,
@@ -193,8 +193,8 @@ static jboolean android_graphics_GraphicBuffer_lockCanvas(JNIEnv* env, jobject,
static jboolean android_graphics_GraphicBuffer_unlockCanvasAndPost(JNIEnv* env, jobject,
jlong wrapperHandle, jobject canvasObj) {
// release the buffer from the canvas
- ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj);
- ACanvas_setBuffer(canvas, nullptr, ADATASPACE_UNKNOWN);
+ graphics::Canvas canvas(env, canvasObj);
+ canvas.setBuffer(nullptr, ADATASPACE_UNKNOWN);
GraphicBufferWrapper* wrapper =
reinterpret_cast<GraphicBufferWrapper*>(wrapperHandle);
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index bf4ffc7e42e0..daf33f61105c 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -352,7 +352,7 @@ static Guarded<AssetManager2>& AssetManagerFromLong(jlong ptr) {
}
static jobject NativeGetOverlayableMap(JNIEnv* env, jclass /*clazz*/, jlong ptr,
- jstring package_name) {
+ jstring package_name) {
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
const ScopedUtfChars package_name_utf8(env, package_name);
CHECK(package_name_utf8.c_str() != nullptr);
@@ -397,6 +397,21 @@ static jobject NativeGetOverlayableMap(JNIEnv* env, jclass /*clazz*/, jlong ptr,
return array_map;
}
+static jstring NativeGetOverlayablesToString(JNIEnv* env, jclass /*clazz*/, jlong ptr,
+ jstring package_name) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ScopedUtfChars package_name_utf8(env, package_name);
+ CHECK(package_name_utf8.c_str() != nullptr);
+ const std::string std_package_name(package_name_utf8.c_str());
+
+ std::string result;
+ if (!assetmanager->GetOverlayablesToString(std_package_name, &result)) {
+ return nullptr;
+ }
+
+ return env->NewStringUTF(result.c_str());
+}
+
#ifdef __ANDROID__ // Layoutlib does not support parcel
static jobject ReturnParcelFileDescriptor(JNIEnv* env, std::unique_ptr<Asset> asset,
jlongArray out_offsets) {
@@ -1608,6 +1623,8 @@ static const JNINativeMethod gAssetManagerMethods[] = {
(void*)NativeCreateIdmapsForStaticOverlaysTargetingAndroid},
{"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;",
(void*)NativeGetOverlayableMap},
+ {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;",
+ (void*)NativeGetOverlayablesToString},
// Global management/debug methods.
{"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
diff --git a/core/jni/android_view_PointerIcon.cpp b/core/jni/android_view_PointerIcon.cpp
index 4f79790dec51..ed2ce506ab23 100644
--- a/core/jni/android_view_PointerIcon.cpp
+++ b/core/jni/android_view_PointerIcon.cpp
@@ -23,7 +23,7 @@
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <utils/Log.h>
-#include <android/graphics/GraphicsJNI.h>
+#include <android/graphics/bitmap.h>
#include <nativehelper/ScopedLocalRef.h>
#include "core_jni_helpers.h"
@@ -88,7 +88,7 @@ status_t android_view_PointerIcon_getLoadedIcon(JNIEnv* env, jobject pointerIcon
ScopedLocalRef<jobject> bitmapObj(
env, env->GetObjectField(pointerIconObj, gPointerIconClassInfo.mBitmap));
if (bitmapObj.get()) {
- GraphicsJNI::getSkBitmap(env, bitmapObj.get(), &(outPointerIcon->bitmap));
+ outPointerIcon->bitmap = graphics::Bitmap(env, bitmapObj.get());
}
ScopedLocalRef<jobjectArray> bitmapFramesObj(env, reinterpret_cast<jobjectArray>(
@@ -100,7 +100,7 @@ status_t android_view_PointerIcon_getLoadedIcon(JNIEnv* env, jobject pointerIcon
outPointerIcon->bitmapFrames.resize(size);
for (jsize i = 0; i < size; ++i) {
ScopedLocalRef<jobject> bitmapObj(env, env->GetObjectArrayElement(bitmapFramesObj.get(), i));
- GraphicsJNI::getSkBitmap(env, bitmapObj.get(), &(outPointerIcon->bitmapFrames[i]));
+ outPointerIcon->bitmapFrames[i] = graphics::Bitmap(env, bitmapObj.get());
}
}
diff --git a/core/jni/android_view_PointerIcon.h b/core/jni/android_view_PointerIcon.h
index 00bdfb4bf04e..908948ea2aa4 100644
--- a/core/jni/android_view_PointerIcon.h
+++ b/core/jni/android_view_PointerIcon.h
@@ -21,8 +21,8 @@
#include <vector>
+#include <android/graphics/bitmap.h>
#include <utils/Errors.h>
-#include <SkBitmap.h>
namespace android {
@@ -68,10 +68,10 @@ struct PointerIcon {
}
int32_t style;
- SkBitmap bitmap;
+ graphics::Bitmap bitmap;
float hotSpotX;
float hotSpotY;
- std::vector<SkBitmap> bitmapFrames;
+ std::vector<graphics::Bitmap> bitmapFrames;
int32_t durationPerFrame;
inline bool isNullIcon() {
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 4c2e91f986d0..058a4c8ee2f9 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -237,12 +237,11 @@ static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
return 0;
}
- ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj);
- ACanvas_setBuffer(canvas, &buffer, static_cast<int32_t>(surface->getBuffersDataSpace()));
+ graphics::Canvas canvas(env, canvasObj);
+ canvas.setBuffer(&buffer, static_cast<int32_t>(surface->getBuffersDataSpace()));
if (dirtyRectPtr) {
- ACanvas_clipRect(canvas, {dirtyRect.left, dirtyRect.top,
- dirtyRect.right, dirtyRect.bottom});
+ canvas.clipRect({dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom});
}
if (dirtyRectObj) {
@@ -268,8 +267,8 @@ static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,
}
// detach the canvas from the surface
- ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj);
- ACanvas_setBuffer(canvas, nullptr, ADATASPACE_UNKNOWN);
+ graphics::Canvas canvas(env, canvasObj);
+ canvas.setBuffer(nullptr, ADATASPACE_UNKNOWN);
// unlock surface
status_t err = surface->unlockAndPost();
diff --git a/core/jni/android_view_TextureLayer.cpp b/core/jni/android_view_TextureLayer.cpp
index 1ccb6a8f610c..8a3f54039d05 100644
--- a/core/jni/android_view_TextureLayer.cpp
+++ b/core/jni/android_view_TextureLayer.cpp
@@ -26,10 +26,7 @@
#include <gui/GLConsumer.h>
#include <hwui/Paint.h>
-#include <SkBitmap.h>
-#include <SkCanvas.h>
#include <SkMatrix.h>
-#include <SkBlendMode.h>
#include <DeferredLayerUpdater.h>
#include <Rect.h>
diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index 1f69c8bbbe5d..391f515af115 100644
--- a/core/jni/android_view_TextureView.cpp
+++ b/core/jni/android_view_TextureView.cpp
@@ -124,9 +124,9 @@ static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject,
int32_t status = native_window_lock(window.get(), &outBuffer, &rect);
if (status) return JNI_FALSE;
- ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj);
- ACanvas_setBuffer(canvas, &outBuffer, ANativeWindow_getBuffersDataSpace(window.get()));
- ACanvas_clipRect(canvas, {rect.left, rect.top, rect.right, rect.bottom});
+ graphics::Canvas canvas(env, canvasObj);
+ canvas.setBuffer(&outBuffer, ANativeWindow_getBuffersDataSpace(window.get()));
+ canvas.clipRect({rect.left, rect.top, rect.right, rect.bottom});
if (dirtyRect) {
INVOKEV(dirtyRect, gRectClassInfo.set,
@@ -140,8 +140,8 @@ static void android_view_TextureView_unlockCanvasAndPost(JNIEnv* env, jobject,
jlong nativeWindow, jobject canvasObj) {
// release the buffer from the canvas
- ACanvas* canvas = ACanvas_getNativeHandleFromJava(env, canvasObj);
- ACanvas_setBuffer(canvas, nullptr, ADATASPACE_UNKNOWN);
+ graphics::Canvas canvas(env, canvasObj);
+ canvas.setBuffer(nullptr, ADATASPACE_UNKNOWN);
if (nativeWindow) {
sp<ANativeWindow> window((ANativeWindow*) nativeWindow);
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 93ef75148df7..3516dce5d5ed 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -74,7 +74,6 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <bionic/malloc.h>
-#include <cutils/ashmem.h>
#include <cutils/fs.h>
#include <cutils/multiuser.h>
#include <cutils/sockets.h>
@@ -1657,11 +1656,6 @@ static void com_android_internal_os_Zygote_nativeInitNativeState(JNIEnv* env, jc
if (!SetTaskProfiles(0, {})) {
ZygoteFailure(env, "zygote", nullptr, "Zygote SetTaskProfiles failed");
}
-
- /*
- * ashmem initialization to avoid dlopen overhead
- */
- ashmem_init();
}
/**
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index f32935fa300f..2dfb777592fd 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1,11 +1,11 @@
{
"version": "1.0.0",
"messages": {
- "594230385": {
+ "676824470": {
"message": "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
"level": "ERROR",
"group": "TEST_GROUP",
- "at": "com\/android\/server\/wm\/ProtoLogGroup.java:94"
+ "at": "com\/android\/server\/wm\/ProtoLogGroup.java"
}
},
"groups": {
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 01caf011f644..eec49df79630 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -224,6 +224,62 @@ const std::unordered_map<std::string, std::string>*
return &loaded_package->GetOverlayableMap();
}
+bool AssetManager2::GetOverlayablesToString(const android::StringPiece& package_name,
+ std::string* out) const {
+ uint8_t package_id = 0U;
+ for (const auto& apk_assets : apk_assets_) {
+ const LoadedArsc* loaded_arsc = apk_assets->GetLoadedArsc();
+ if (loaded_arsc == nullptr) {
+ continue;
+ }
+
+ const auto& loaded_packages = loaded_arsc->GetPackages();
+ if (loaded_packages.empty()) {
+ continue;
+ }
+
+ const auto& loaded_package = loaded_packages[0];
+ if (loaded_package->GetPackageName() == package_name) {
+ package_id = GetAssignedPackageId(loaded_package.get());
+ break;
+ }
+ }
+
+ if (package_id == 0U) {
+ ANDROID_LOG(ERROR) << base::StringPrintf("No package with name '%s", package_name.data());
+ return false;
+ }
+
+ const size_t idx = package_ids_[package_id];
+ if (idx == 0xff) {
+ return false;
+ }
+
+ std::string output;
+ for (const ConfiguredPackage& package : package_groups_[idx].packages_) {
+ const LoadedPackage* loaded_package = package.loaded_package_;
+ for (auto it = loaded_package->begin(); it != loaded_package->end(); it++) {
+ const OverlayableInfo* info = loaded_package->GetOverlayableInfo(*it);
+ if (info != nullptr) {
+ ResourceName res_name;
+ if (!GetResourceName(*it, &res_name)) {
+ ANDROID_LOG(ERROR) << base::StringPrintf(
+ "Unable to retrieve name of overlayable resource 0x%08x", *it);
+ return false;
+ }
+
+ const std::string name = ToFormattedResourceString(&res_name);
+ output.append(base::StringPrintf(
+ "resource='%s' overlayable='%s' actor='%s' policy='0x%08x'\n",
+ name.c_str(), info->name.c_str(), info->actor.c_str(), info->policy_flags));
+ }
+ }
+ }
+
+ *out = std::move(output);
+ return true;
+}
+
void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
const int diff = configuration_.diff(configuration);
configuration_ = configuration;
@@ -1073,7 +1129,7 @@ void AssetManager2::InvalidateCaches(uint32_t diff) {
}
}
-uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) {
+uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) const {
for (auto& package_group : package_groups_) {
for (auto& package2 : package_group.packages_) {
if (package2.loaded_package_ == package) {
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 1e2b36cb1703..de46081a6aa3 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -124,6 +124,10 @@ class AssetManager2 {
// This may be nullptr if the APK represented by `cookie` has no resource table.
const DynamicRefTable* GetDynamicRefTableForCookie(ApkAssetsCookie cookie) const;
+ // Returns a string representation of the overlayable API of a package.
+ bool GetOverlayablesToString(const android::StringPiece& package_name,
+ std::string* out) const;
+
const std::unordered_map<std::string, std::string>*
GetOverlayableMapForPackage(uint32_t package_id) const;
@@ -308,7 +312,7 @@ class AssetManager2 {
const ResolvedBag* GetBag(uint32_t resid, std::vector<uint32_t>& child_resids);
// Retrieve the assigned package id of the package if loaded into this AssetManager
- uint8_t GetAssignedPackageId(const LoadedPackage* package);
+ uint8_t GetAssignedPackageId(const LoadedPackage* package) const;
// The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
// have a longer lifetime.
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 40c8e46e4d84..15910241518d 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -707,7 +707,7 @@ TEST_F(AssetManager2Test, GetLastPathAfterDisablingReturnsEmpty) {
EXPECT_EQ("", resultDisabled);
}
-TEST_F(AssetManager2Test, GetOverlayableMap) {
+TEST_F(AssetManager2Test, GetOverlayablesToString) {
ResTable_config desired_config;
memset(&desired_config, 0, sizeof(desired_config));
@@ -721,6 +721,12 @@ TEST_F(AssetManager2Test, GetOverlayableMap) {
ASSERT_EQ(2, map->size());
ASSERT_EQ(map->at("OverlayableResources1"), "overlay://theme");
ASSERT_EQ(map->at("OverlayableResources2"), "overlay://com.android.overlayable");
+
+ std::string api;
+ ASSERT_TRUE(assetmanager.GetOverlayablesToString("com.android.overlayable", &api));
+ ASSERT_EQ(api.find("not_overlayable"), std::string::npos);
+ ASSERT_NE(api.find("resource='com.android.overlayable:string/overlayable2' overlayable='OverlayableResources1' actor='overlay://theme' policy='0x0000000a'\n"),
+ std::string::npos);
}
} // namespace android
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 16f2917f8df8..6bb896fd7b29 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -20,11 +20,11 @@ cc_library_shared {
],
shared_libs: [
+ "libandroid_runtime",
"libbinder",
"libcutils",
"liblog",
"libutils",
- "libhwui",
"libgui",
"libui",
"libinput",
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index abf083789c23..e4348f2a9b21 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -24,12 +24,6 @@
#include <log/log.h>
-#include <SkBitmap.h>
-#include <SkCanvas.h>
-#include <SkColor.h>
-#include <SkPaint.h>
-#include <SkBlendMode.h>
-
namespace android {
// --- WeakLooperCallback ---
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index fd386e9f7a8a..804644c230b9 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -23,11 +23,9 @@
#include <utils/String8.h>
#include <gui/Surface.h>
-#include <SkBitmap.h>
-#include <SkCanvas.h>
-#include <SkColor.h>
-#include <SkPaint.h>
-
+#include <android/graphics/bitmap.h>
+#include <android/graphics/canvas.h>
+#include <android/graphics/paint.h>
#include <android/native_window.h>
namespace android {
@@ -132,8 +130,8 @@ void SpriteController::doUpdateSprites() {
SpriteUpdate& update = updates.editItemAt(i);
if (update.state.surfaceControl == NULL && update.state.wantSurfaceVisible()) {
- update.state.surfaceWidth = update.state.icon.bitmap.width();
- update.state.surfaceHeight = update.state.icon.bitmap.height();
+ update.state.surfaceWidth = update.state.icon.bitmap.getInfo().width;
+ update.state.surfaceHeight = update.state.icon.bitmap.getInfo().height;
update.state.surfaceDrawn = false;
update.state.surfaceVisible = false;
update.state.surfaceControl = obtainSurface(
@@ -154,8 +152,8 @@ void SpriteController::doUpdateSprites() {
}
if (update.state.wantSurfaceVisible()) {
- int32_t desiredWidth = update.state.icon.bitmap.width();
- int32_t desiredHeight = update.state.icon.bitmap.height();
+ int32_t desiredWidth = update.state.icon.bitmap.getInfo().width;
+ int32_t desiredHeight = update.state.icon.bitmap.getInfo().height;
if (update.state.surfaceWidth < desiredWidth
|| update.state.surfaceHeight < desiredHeight) {
needApplyTransaction = true;
@@ -201,26 +199,22 @@ void SpriteController::doUpdateSprites() {
if (status) {
ALOGE("Error %d locking sprite surface before drawing.", status);
} else {
- SkBitmap surfaceBitmap;
- ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
- surfaceBitmap.installPixels(SkImageInfo::MakeN32Premul(outBuffer.width, outBuffer.height),
- outBuffer.bits, bpr);
+ graphics::Paint paint;
+ paint.setBlendMode(ABLEND_MODE_SRC);
- SkCanvas surfaceCanvas(surfaceBitmap);
+ graphics::Canvas canvas(outBuffer, (int32_t) surface->getBuffersDataSpace());
+ canvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint);
- SkPaint paint;
- paint.setBlendMode(SkBlendMode::kSrc);
- surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint);
+ const int iconWidth = update.state.icon.bitmap.getInfo().width;
+ const int iconHeight = update.state.icon.bitmap.getInfo().height;
- if (outBuffer.width > update.state.icon.bitmap.width()) {
- paint.setColor(0); // transparent fill color
- surfaceCanvas.drawRect(SkRect::MakeLTRB(update.state.icon.bitmap.width(), 0,
- outBuffer.width, update.state.icon.bitmap.height()), paint);
+ if (outBuffer.width > iconWidth) {
+ paint.setBlendMode(ABLEND_MODE_CLEAR); // clear to transparent
+ canvas.drawRect({iconWidth, 0, outBuffer.width, iconHeight}, paint);
}
- if (outBuffer.height > update.state.icon.bitmap.height()) {
- paint.setColor(0); // transparent fill color
- surfaceCanvas.drawRect(SkRect::MakeLTRB(0, update.state.icon.bitmap.height(),
- outBuffer.width, outBuffer.height), paint);
+ if (outBuffer.height > iconHeight) {
+ paint.setBlendMode(ABLEND_MODE_CLEAR); // clear to transparent
+ canvas.drawRect({0, iconHeight, outBuffer.width, outBuffer.height}, paint);
}
status = surface->unlockAndPost();
@@ -398,12 +392,7 @@ void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) {
uint32_t dirty;
if (icon.isValid()) {
- SkBitmap* bitmapCopy = &mLocked.state.icon.bitmap;
- if (bitmapCopy->tryAllocPixels(icon.bitmap.info().makeColorType(kN32_SkColorType))) {
- icon.bitmap.readPixels(bitmapCopy->info(), bitmapCopy->getPixels(),
- bitmapCopy->rowBytes(), 0, 0);
- }
-
+ mLocked.state.icon.bitmap = icon.bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888);
if (!mLocked.state.icon.isValid()
|| mLocked.state.icon.hotSpotX != icon.hotSpotX
|| mLocked.state.icon.hotSpotY != icon.hotSpotY) {
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 79a904f5fe65..2513544d4bdf 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -20,10 +20,9 @@
#include <utils/RefBase.h>
#include <utils/Looper.h>
+#include <android/graphics/bitmap.h>
#include <gui/SurfaceComposerClient.h>
-#include <SkBitmap.h>
-
namespace android {
/*
@@ -56,21 +55,16 @@ struct SpriteTransformationMatrix {
*/
struct SpriteIcon {
inline SpriteIcon() : style(0), hotSpotX(0), hotSpotY(0) { }
- inline SpriteIcon(const SkBitmap& bitmap, int32_t style, float hotSpotX, float hotSpotY) :
+ inline SpriteIcon(const graphics::Bitmap& bitmap, int32_t style, float hotSpotX, float hotSpotY) :
bitmap(bitmap), style(style), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { }
- SkBitmap bitmap;
+ graphics::Bitmap bitmap;
int32_t style;
float hotSpotX;
float hotSpotY;
inline SpriteIcon copy() const {
- SkBitmap bitmapCopy;
- if (bitmapCopy.tryAllocPixels(bitmap.info().makeColorType(kN32_SkColorType))) {
- bitmap.readPixels(bitmapCopy.info(), bitmapCopy.getPixels(), bitmapCopy.rowBytes(),
- 0, 0);
- }
- return SpriteIcon(bitmapCopy, style, hotSpotX, hotSpotY);
+ return SpriteIcon(bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888), style, hotSpotX, hotSpotY);
}
inline void reset() {
@@ -81,7 +75,7 @@ struct SpriteIcon {
}
inline bool isValid() const {
- return !bitmap.isNull() && !bitmap.empty();
+ return bitmap.isValid() && !bitmap.isEmpty();
}
};
@@ -183,7 +177,7 @@ private:
* This structure is designed so that it can be copied during updates so that
* surfaces can be resized and redrawn without blocking the client by holding a lock
* on the sprites for a long time.
- * Note that the SkBitmap holds a reference to a shared (and immutable) pixel ref. */
+ * Note that the SpriteIcon holds a reference to a shared (and immutable) bitmap. */
struct SpriteState {
inline SpriteState() :
dirty(0), visible(false),
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index e83b2a78d180..b1e3d6fe845a 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -18,9 +18,9 @@ cc_test {
"PointerController_test.cpp",
],
shared_libs: [
+ "libandroid_runtime",
"libinputservice",
"libgui",
- "libhwui",
"libutils",
],
static_libs: [
diff --git a/opengl/java/android/opengl/GLUtils.java b/opengl/java/android/opengl/GLUtils.java
index ca8d5ac52021..cc46514ae96e 100644
--- a/opengl/java/android/opengl/GLUtils.java
+++ b/opengl/java/android/opengl/GLUtils.java
@@ -44,7 +44,7 @@ public final class GLUtils {
if (bitmap.isRecycled()) {
throw new IllegalArgumentException("bitmap is recycled");
}
- int result = native_getInternalFormat(bitmap.getNativeInstance());
+ int result = native_getInternalFormat(bitmap);
if (result < 0) {
throw new IllegalArgumentException("Unknown internalformat");
}
@@ -66,7 +66,7 @@ public final class GLUtils {
if (bitmap.isRecycled()) {
throw new IllegalArgumentException("bitmap is recycled");
}
- int result = native_getType(bitmap.getNativeInstance());
+ int result = native_getType(bitmap);
if (result < 0) {
throw new IllegalArgumentException("Unknown type");
}
@@ -103,8 +103,7 @@ public final class GLUtils {
if (bitmap.isRecycled()) {
throw new IllegalArgumentException("bitmap is recycled");
}
- if (native_texImage2D(target, level, internalformat, bitmap.getNativeInstance(), -1,
- border) != 0) {
+ if (native_texImage2D(target, level, internalformat, bitmap, -1, border) != 0) {
throw new IllegalArgumentException("invalid Bitmap format");
}
}
@@ -130,8 +129,7 @@ public final class GLUtils {
if (bitmap.isRecycled()) {
throw new IllegalArgumentException("bitmap is recycled");
}
- if (native_texImage2D(target, level, internalformat, bitmap.getNativeInstance(), type,
- border) != 0) {
+ if (native_texImage2D(target, level, internalformat, bitmap, type, border) != 0) {
throw new IllegalArgumentException("invalid Bitmap format");
}
}
@@ -153,7 +151,7 @@ public final class GLUtils {
if (bitmap.isRecycled()) {
throw new IllegalArgumentException("bitmap is recycled");
}
- if (native_texImage2D(target, level, -1, bitmap.getNativeInstance(), -1, border) != 0) {
+ if (native_texImage2D(target, level, -1, bitmap, -1, border) != 0) {
throw new IllegalArgumentException("invalid Bitmap format");
}
}
@@ -189,8 +187,7 @@ public final class GLUtils {
throw new IllegalArgumentException("bitmap is recycled");
}
int type = getType(bitmap);
- if (native_texSubImage2D(target, level, xoffset, yoffset, bitmap.getNativeInstance(), -1,
- type) != 0) {
+ if (native_texSubImage2D(target, level, xoffset, yoffset, bitmap, -1, type) != 0) {
throw new IllegalArgumentException("invalid Bitmap format");
}
}
@@ -214,8 +211,7 @@ public final class GLUtils {
if (bitmap.isRecycled()) {
throw new IllegalArgumentException("bitmap is recycled");
}
- if (native_texSubImage2D(target, level, xoffset, yoffset, bitmap.getNativeInstance(),
- format, type) != 0) {
+ if (native_texSubImage2D(target, level, xoffset, yoffset, bitmap, format, type) != 0) {
throw new IllegalArgumentException("invalid Bitmap format");
}
}
@@ -265,10 +261,10 @@ public final class GLUtils {
}
}
- native private static int native_getInternalFormat(long bitmapHandle);
- native private static int native_getType(long bitmapHandle);
- native private static int native_texImage2D(int target, int level, int internalformat,
- long bitmapHandle, int type, int border);
- native private static int native_texSubImage2D(int target, int level, int xoffset, int yoffset,
- long bitmapHandle, int format, int type);
+ private static native int native_getInternalFormat(Bitmap bitmap);
+ private static native int native_getType(Bitmap bitmap);
+ private static native int native_texImage2D(int target, int level, int internalformat,
+ Bitmap bitmap, int type, int border);
+ private static native int native_texSubImage2D(int target, int level, int xoffset, int yoffset,
+ Bitmap bitmap, int format, int type);
}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDownloader.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDownloader.java
new file mode 100644
index 000000000000..afcca79a0027
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDownloader.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 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;
+
+import java.io.IOException;
+
+/** Interface for classes which will provide backup data */
+public abstract class FullRestoreDownloader {
+ /** Enum to provide information on why a download finished */
+ public enum FinishType {
+ UNKNOWN_FINISH(0),
+ // Finish the downloading and successfully write data to Android OS.
+ FINISHED(1),
+ // Download failed with any kind of exception.
+ TRANSFER_FAILURE(2),
+ // Download failed due to auth failure on the device.
+ AUTH_FAILURE(3),
+ // Aborted by Android Framework.
+ FRAMEWORK_ABORTED(4);
+
+ private int mValue;
+
+ FinishType(int value) {
+ mValue = value;
+ }
+ }
+
+ /** Get the next data chunk from the backing store */
+ public abstract int readNextChunk(byte[] buffer) throws IOException;
+
+ /** Called when we've finished restoring the data */
+ public abstract void finish(FinishType finishType);
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupFileDecryptorTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupFileDecryptorTask.java
new file mode 100644
index 000000000000..9bf148ddc901
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupFileDecryptorTask.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2019 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.tasks;
+
+import android.util.Slog;
+import android.util.SparseIntArray;
+
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkOrdering;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunksMetadata;
+
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Locale;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.GCMParameterSpec;
+
+/**
+ * A backup file consists of, in order:
+ *
+ * <ul>
+ * <li>A randomly ordered sequence of encrypted chunks
+ * <li>A plaintext {@link ChunksMetadata} proto, containing the bytes of an encrypted {@link
+ * ChunkOrdering} proto.
+ * <li>A 64-bit long denoting the offset of the file at which the ChunkOrdering proto starts.
+ * </ul>
+ *
+ * <p>This task decrypts such a blob and writes the plaintext to another file.
+ *
+ * <p>The backup file has two formats to indicate the boundaries of the chunks in the encrypted
+ * file. In {@link ChunksMetadataProto#EXPLICIT_STARTS} mode the chunk ordering contains the start
+ * positions of each chunk and the decryptor outputs the chunks in the order they appeared in the
+ * plaintext file. In {@link ChunksMetadataProto#INLINE_LENGTHS} mode the length of each encrypted
+ * chunk is prepended to the chunk in the file and the decryptor outputs the chunks in no specific
+ * order.
+ *
+ * <p>{@link ChunksMetadataProto#EXPLICIT_STARTS} is for use with full backup (Currently used for
+ * all backups as b/77188289 is not implemented yet), {@link ChunksMetadataProto#INLINE_LENGTHS}
+ * will be used for kv backup (once b/77188289 is implemented) to avoid re-uploading the chunk
+ * ordering (see b/70782620).
+ */
+public class BackupFileDecryptorTask {
+ private static final String TAG = "BackupFileDecryptorTask";
+
+ private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+ private static final int GCM_NONCE_LENGTH_BYTES = 12;
+ private static final int GCM_TAG_LENGTH_BYTES = 16;
+ private static final int BITS_PER_BYTE = 8;
+ private static final String READ_MODE = "r";
+ private static final int BYTES_PER_LONG = 64 / BITS_PER_BYTE;
+
+ private final Cipher mCipher;
+ private final SecretKey mSecretKey;
+
+ /**
+ * A new instance.
+ *
+ * @param secretKey The tertiary key used to encrypt the backup blob.
+ */
+ public BackupFileDecryptorTask(SecretKey secretKey)
+ throws NoSuchPaddingException, NoSuchAlgorithmException {
+ this.mCipher = Cipher.getInstance(CIPHER_ALGORITHM);
+ this.mSecretKey = secretKey;
+ }
+
+ /**
+ * Runs the task, reading the encrypted data from {@code input} and writing the plaintext data
+ * to {@code output}.
+ *
+ * @param inputFile The encrypted backup file.
+ * @param decryptedChunkOutput Unopened output to write the plaintext to, which this class will
+ * open and close during decryption.
+ * @throws IOException if an error occurred reading the encrypted file or writing the plaintext,
+ * or if one of the protos could not be deserialized.
+ */
+ public void decryptFile(File inputFile, DecryptedChunkOutput decryptedChunkOutput)
+ throws IOException, EncryptedRestoreException, IllegalBlockSizeException,
+ BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException,
+ ShortBufferException, NoSuchAlgorithmException {
+ RandomAccessFile input = new RandomAccessFile(inputFile, READ_MODE);
+
+ long metadataOffset = getChunksMetadataOffset(input);
+ ChunksMetadataProto.ChunksMetadata chunksMetadata =
+ getChunksMetadata(input, metadataOffset);
+ ChunkOrdering chunkOrdering = decryptChunkOrdering(chunksMetadata);
+
+ if (chunksMetadata.chunkOrderingType == ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED
+ || chunksMetadata.chunkOrderingType == ChunksMetadataProto.EXPLICIT_STARTS) {
+ Slog.d(TAG, "Using explicit starts");
+ decryptFileWithExplicitStarts(
+ input, decryptedChunkOutput, chunkOrdering, metadataOffset);
+
+ } else if (chunksMetadata.chunkOrderingType == ChunksMetadataProto.INLINE_LENGTHS) {
+ Slog.d(TAG, "Using inline lengths");
+ decryptFileWithInlineLengths(input, decryptedChunkOutput, metadataOffset);
+
+ } else {
+ throw new UnsupportedEncryptedFileException(
+ "Unknown chunk ordering type:" + chunksMetadata.chunkOrderingType);
+ }
+
+ if (!Arrays.equals(decryptedChunkOutput.getDigest(), chunkOrdering.checksum)) {
+ throw new MessageDigestMismatchException("Checksums did not match");
+ }
+ }
+
+ private void decryptFileWithExplicitStarts(
+ RandomAccessFile input,
+ DecryptedChunkOutput decryptedChunkOutput,
+ ChunkOrdering chunkOrdering,
+ long metadataOffset)
+ throws IOException, InvalidKeyException, IllegalBlockSizeException,
+ InvalidAlgorithmParameterException, ShortBufferException, BadPaddingException,
+ NoSuchAlgorithmException {
+ SparseIntArray chunkLengthsByPosition =
+ getChunkLengths(chunkOrdering.starts, (int) metadataOffset);
+ int largestChunkLength = getLargestChunkLength(chunkLengthsByPosition);
+ byte[] encryptedChunkBuffer = new byte[largestChunkLength];
+ // largestChunkLength is 0 if the backup file contains zero chunks e.g. 0 kv pairs.
+ int plaintextBufferLength =
+ Math.max(0, largestChunkLength - GCM_NONCE_LENGTH_BYTES - GCM_TAG_LENGTH_BYTES);
+ byte[] plaintextChunkBuffer = new byte[plaintextBufferLength];
+
+ try (DecryptedChunkOutput output = decryptedChunkOutput.open()) {
+ for (int start : chunkOrdering.starts) {
+ int length = chunkLengthsByPosition.get(start);
+
+ input.seek(start);
+ input.readFully(encryptedChunkBuffer, 0, length);
+ int plaintextLength =
+ decryptChunk(encryptedChunkBuffer, length, plaintextChunkBuffer);
+ outputChunk(output, plaintextChunkBuffer, plaintextLength);
+ }
+ }
+ }
+
+ private void decryptFileWithInlineLengths(
+ RandomAccessFile input, DecryptedChunkOutput decryptedChunkOutput, long metadataOffset)
+ throws MalformedEncryptedFileException, IOException, IllegalBlockSizeException,
+ BadPaddingException, InvalidAlgorithmParameterException, ShortBufferException,
+ InvalidKeyException, NoSuchAlgorithmException {
+ input.seek(0);
+ try (DecryptedChunkOutput output = decryptedChunkOutput.open()) {
+ while (input.getFilePointer() < metadataOffset) {
+ long start = input.getFilePointer();
+ int encryptedChunkLength = input.readInt();
+
+ if (encryptedChunkLength <= 0) {
+ // If the length of the encrypted chunk is not positive we will not make
+ // progress reading the file and so will loop forever.
+ throw new MalformedEncryptedFileException(
+ "Encrypted chunk length not positive:" + encryptedChunkLength);
+ }
+
+ if (start + encryptedChunkLength > metadataOffset) {
+ throw new MalformedEncryptedFileException(
+ String.format(
+ Locale.US,
+ "Encrypted chunk longer (%d) than file (%d)",
+ encryptedChunkLength,
+ metadataOffset));
+ }
+
+ byte[] plaintextChunk = new byte[encryptedChunkLength];
+ byte[] plaintext =
+ new byte
+ [encryptedChunkLength
+ - GCM_NONCE_LENGTH_BYTES
+ - GCM_TAG_LENGTH_BYTES];
+
+ input.readFully(plaintextChunk);
+
+ int plaintextChunkLength =
+ decryptChunk(plaintextChunk, encryptedChunkLength, plaintext);
+ outputChunk(output, plaintext, plaintextChunkLength);
+ }
+ }
+ }
+
+ private void outputChunk(
+ DecryptedChunkOutput output, byte[] plaintextChunkBuffer, int plaintextLength)
+ throws IOException, InvalidKeyException, NoSuchAlgorithmException {
+ output.processChunk(plaintextChunkBuffer, plaintextLength);
+ }
+
+ /**
+ * Decrypts chunk and returns the length of the plaintext.
+ *
+ * @param encryptedChunkBuffer The encrypted data, prefixed by the nonce.
+ * @param encryptedChunkBufferLength The length of the encrypted chunk (including nonce).
+ * @param plaintextChunkBuffer The buffer into which to write the plaintext chunk.
+ * @return The length of the plaintext chunk.
+ */
+ private int decryptChunk(
+ byte[] encryptedChunkBuffer,
+ int encryptedChunkBufferLength,
+ byte[] plaintextChunkBuffer)
+ throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException,
+ ShortBufferException, IllegalBlockSizeException {
+
+ mCipher.init(
+ Cipher.DECRYPT_MODE,
+ mSecretKey,
+ new GCMParameterSpec(
+ GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE,
+ encryptedChunkBuffer,
+ 0,
+ GCM_NONCE_LENGTH_BYTES));
+
+ return mCipher.doFinal(
+ encryptedChunkBuffer,
+ GCM_NONCE_LENGTH_BYTES,
+ encryptedChunkBufferLength - GCM_NONCE_LENGTH_BYTES,
+ plaintextChunkBuffer);
+ }
+
+ /** Given all the lengths, returns the largest length. */
+ private int getLargestChunkLength(SparseIntArray lengths) {
+ int maxSeen = 0;
+ for (int i = 0; i < lengths.size(); i++) {
+ maxSeen = Math.max(maxSeen, lengths.valueAt(i));
+ }
+ return maxSeen;
+ }
+
+ /**
+ * From a list of the starting position of each chunk in the correct order of the backup data,
+ * calculates a mapping from start position to length of that chunk.
+ *
+ * @param starts The start positions of chunks, in order.
+ * @param chunkOrderingPosition Where the {@link ChunkOrdering} proto starts, used to calculate
+ * the length of the last chunk.
+ * @return The mapping.
+ */
+ private SparseIntArray getChunkLengths(int[] starts, int chunkOrderingPosition) {
+ int[] boundaries = Arrays.copyOf(starts, starts.length + 1);
+ boundaries[boundaries.length - 1] = chunkOrderingPosition;
+ Arrays.sort(boundaries);
+
+ SparseIntArray lengths = new SparseIntArray();
+ for (int i = 0; i < boundaries.length - 1; i++) {
+ lengths.put(boundaries[i], boundaries[i + 1] - boundaries[i]);
+ }
+ return lengths;
+ }
+
+ /**
+ * Reads and decrypts the {@link ChunkOrdering} from the {@link ChunksMetadata}.
+ *
+ * @param metadata The metadata.
+ * @return The ordering.
+ * @throws InvalidProtocolBufferNanoException if there is an issue deserializing the proto.
+ */
+ private ChunkOrdering decryptChunkOrdering(ChunksMetadata metadata)
+ throws InvalidProtocolBufferNanoException, InvalidAlgorithmParameterException,
+ InvalidKeyException, BadPaddingException, IllegalBlockSizeException,
+ UnsupportedEncryptedFileException {
+ assertCryptoSupported(metadata);
+
+ mCipher.init(
+ Cipher.DECRYPT_MODE,
+ mSecretKey,
+ new GCMParameterSpec(
+ GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE,
+ metadata.chunkOrdering,
+ 0,
+ GCM_NONCE_LENGTH_BYTES));
+
+ byte[] decrypted =
+ mCipher.doFinal(
+ metadata.chunkOrdering,
+ GCM_NONCE_LENGTH_BYTES,
+ metadata.chunkOrdering.length - GCM_NONCE_LENGTH_BYTES);
+
+ return ChunkOrdering.parseFrom(decrypted);
+ }
+
+ /**
+ * Asserts that the Cipher and MessageDigest algorithms in the backup metadata are supported.
+ * For now we only support SHA-256 for checksum and 256-bit AES/GCM/NoPadding for the Cipher.
+ *
+ * @param chunksMetadata The file metadata.
+ * @throws UnsupportedEncryptedFileException if any algorithm is unsupported.
+ */
+ private void assertCryptoSupported(ChunksMetadata chunksMetadata)
+ throws UnsupportedEncryptedFileException {
+ if (chunksMetadata.checksumType != ChunksMetadataProto.SHA_256) {
+ // For now we only support SHA-256.
+ throw new UnsupportedEncryptedFileException(
+ "Unrecognized checksum type for backup (this version of backup only supports"
+ + " SHA-256): "
+ + chunksMetadata.checksumType);
+ }
+
+ if (chunksMetadata.cipherType != ChunksMetadataProto.AES_256_GCM) {
+ throw new UnsupportedEncryptedFileException(
+ "Unrecognized cipher type for backup (this version of backup only supports"
+ + " AES-256-GCM: "
+ + chunksMetadata.cipherType);
+ }
+ }
+
+ /**
+ * Reads the offset of the {@link ChunksMetadata} proto from the end of the file.
+ *
+ * @return The offset.
+ * @throws IOException if there is an error reading.
+ */
+ private long getChunksMetadataOffset(RandomAccessFile input) throws IOException {
+ input.seek(input.length() - BYTES_PER_LONG);
+ return input.readLong();
+ }
+
+ /**
+ * Reads the {@link ChunksMetadata} proto from the given position in the file.
+ *
+ * @param input The encrypted file.
+ * @param position The position where the proto starts.
+ * @return The proto.
+ * @throws IOException if there is an issue reading the file or deserializing the proto.
+ */
+ private ChunksMetadata getChunksMetadata(RandomAccessFile input, long position)
+ throws IOException, MalformedEncryptedFileException {
+ long length = input.length();
+ if (position >= length || position < 0) {
+ throw new MalformedEncryptedFileException(
+ String.format(
+ Locale.US,
+ "%d is not valid position for chunks metadata in file of %d bytes",
+ position,
+ length));
+ }
+
+ // Read chunk ordering bytes
+ input.seek(position);
+ long chunksMetadataLength = input.length() - BYTES_PER_LONG - position;
+ byte[] chunksMetadataBytes = new byte[(int) chunksMetadataLength];
+ input.readFully(chunksMetadataBytes);
+
+ try {
+ return ChunksMetadata.parseFrom(chunksMetadataBytes);
+ } catch (InvalidProtocolBufferNanoException e) {
+ throw new MalformedEncryptedFileException(
+ String.format(
+ Locale.US,
+ "Could not read chunks metadata at position %d of file of %d bytes",
+ position,
+ length));
+ }
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTask.java
new file mode 100644
index 000000000000..82f83f9b7494
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTask.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 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.tasks;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.backup.encryption.FullRestoreDownloader;
+import com.android.server.backup.encryption.FullRestoreDownloader.FinishType;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Reads a stream from a {@link FullRestoreDownloader} and writes it to a file for consumption by
+ * {@link BackupFileDecryptorTask}.
+ */
+public class FullRestoreToFileTask {
+ /**
+ * Maximum number of bytes which the framework can request from the full restore data stream in
+ * one call to {@link BackupTransport#getNextFullRestoreDataChunk}.
+ */
+ public static final int MAX_BYTES_FULL_RESTORE_CHUNK = 1024 * 32;
+
+ /** Returned when the end of a backup stream has been reached. */
+ private static final int END_OF_STREAM = -1;
+
+ private final FullRestoreDownloader mFullRestoreDownloader;
+ private final int mBufferSize;
+
+ /**
+ * Constructs a new instance which reads from the given package wrapper, using a buffer of size
+ * {@link #MAX_BYTES_FULL_RESTORE_CHUNK}.
+ */
+ public FullRestoreToFileTask(FullRestoreDownloader fullRestoreDownloader) {
+ this(fullRestoreDownloader, MAX_BYTES_FULL_RESTORE_CHUNK);
+ }
+
+ @VisibleForTesting
+ FullRestoreToFileTask(FullRestoreDownloader fullRestoreDownloader, int bufferSize) {
+ checkArgument(bufferSize > 0, "Buffer must have positive size");
+
+ this.mFullRestoreDownloader = fullRestoreDownloader;
+ this.mBufferSize = bufferSize;
+ }
+
+ /**
+ * Downloads the backup file from the server and writes it to the given file.
+ *
+ * <p>At the end of the download (success or failure), closes the connection and sends a
+ * Clearcut log.
+ */
+ public void restoreToFile(File targetFile) throws IOException {
+ try (BufferedOutputStream outputStream =
+ new BufferedOutputStream(new FileOutputStream(targetFile))) {
+ byte[] buffer = new byte[mBufferSize];
+ int bytesRead = mFullRestoreDownloader.readNextChunk(buffer);
+ while (bytesRead != END_OF_STREAM) {
+ outputStream.write(buffer, /* off=*/ 0, bytesRead);
+ bytesRead = mFullRestoreDownloader.readNextChunk(buffer);
+ }
+
+ outputStream.flush();
+
+ mFullRestoreDownloader.finish(FinishType.FINISHED);
+ } catch (IOException e) {
+ mFullRestoreDownloader.finish(FinishType.TRANSFER_FAILURE);
+ throw e;
+ }
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/MalformedEncryptedFileException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/MalformedEncryptedFileException.java
new file mode 100644
index 000000000000..78c370b0d548
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/MalformedEncryptedFileException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 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.tasks;
+
+/** Exception thrown when we cannot parse the encrypted backup file. */
+public class MalformedEncryptedFileException extends EncryptedRestoreException {
+ public MalformedEncryptedFileException(String message) {
+ super(message);
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/MessageDigestMismatchException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/MessageDigestMismatchException.java
new file mode 100644
index 000000000000..1e4f43b43e26
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/MessageDigestMismatchException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 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.tasks;
+
+/**
+ * Error thrown if the message digest of the plaintext backup does not match that in the {@link
+ * com.android.server.backup.encryption.protos.ChunksMetadataProto.ChunkOrdering}.
+ */
+public class MessageDigestMismatchException extends EncryptedRestoreException {
+ public MessageDigestMismatchException(String message) {
+ super(message);
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/UnsupportedEncryptedFileException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/UnsupportedEncryptedFileException.java
new file mode 100644
index 000000000000..9a97e3870d83
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/UnsupportedEncryptedFileException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2019 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.tasks;
+
+/**
+ * Thrown when the backup file provided by the server uses encryption algorithms this version of
+ * backup does not support. This could happen if the backup was created with a newer version of the
+ * code.
+ */
+public class UnsupportedEncryptedFileException extends EncryptedRestoreException {
+ public UnsupportedEncryptedFileException(String message) {
+ super(message);
+ }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/BackupFileDecryptorTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/BackupFileDecryptorTaskTest.java
new file mode 100644
index 000000000000..07a6fd2d5b60
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/BackupFileDecryptorTaskTest.java
@@ -0,0 +1,583 @@
+/*
+ * Copyright (C) 2019 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.tasks;
+
+import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;
+import static com.android.server.backup.testing.CryptoTestUtils.newChunkOrdering;
+import static com.android.server.backup.testing.CryptoTestUtils.newChunksMetadata;
+import static com.android.server.backup.testing.CryptoTestUtils.newPair;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.annotation.Nullable;
+import android.app.backup.BackupDataInput;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.ChunkHasher;
+import com.android.server.backup.encryption.chunking.DecryptedChunkFileOutput;
+import com.android.server.backup.encryption.chunking.EncryptedChunk;
+import com.android.server.backup.encryption.chunking.cdc.FingerprintMixer;
+import com.android.server.backup.encryption.kv.DecryptedChunkKvOutput;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkOrdering;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunksMetadata;
+import com.android.server.backup.encryption.protos.nano.KeyValuePairProto.KeyValuePair;
+import com.android.server.backup.encryption.tasks.BackupEncrypter.Result;
+import com.android.server.backup.testing.CryptoTestUtils;
+import com.android.server.testing.shadows.ShadowBackupDataInput;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.protobuf.nano.MessageNano;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.RandomAccessFile;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+import javax.crypto.AEADBadTagException;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
+@Config(shadows = {ShadowBackupDataInput.class})
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class BackupFileDecryptorTaskTest {
+ private static final String READ_WRITE_MODE = "rw";
+ private static final int BYTES_PER_KILOBYTE = 1024;
+ private static final int MIN_CHUNK_SIZE_BYTES = 2 * BYTES_PER_KILOBYTE;
+ private static final int AVERAGE_CHUNK_SIZE_BYTES = 4 * BYTES_PER_KILOBYTE;
+ private static final int MAX_CHUNK_SIZE_BYTES = 64 * BYTES_PER_KILOBYTE;
+ private static final int BACKUP_DATA_SIZE_BYTES = 60 * BYTES_PER_KILOBYTE;
+ private static final int GCM_NONCE_LENGTH_BYTES = 12;
+ private static final int GCM_TAG_LENGTH_BYTES = 16;
+ private static final int BITS_PER_BYTE = 8;
+ private static final int CHECKSUM_LENGTH_BYTES = 256 / BITS_PER_BYTE;
+ @Nullable private static final FileDescriptor NULL_FILE_DESCRIPTOR = null;
+
+ private static final Set<KeyValuePair> TEST_KV_DATA = new HashSet<>();
+
+ static {
+ TEST_KV_DATA.add(newPair("key1", "value1"));
+ TEST_KV_DATA.add(newPair("key2", "value2"));
+ }
+
+ @Rule public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ private SecretKey mTertiaryKey;
+ private SecretKey mChunkEncryptionKey;
+ private File mInputFile;
+ private File mOutputFile;
+ private DecryptedChunkOutput mFileOutput;
+ private DecryptedChunkKvOutput mKvOutput;
+ private Random mRandom;
+ private BackupFileDecryptorTask mTask;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mRandom = new Random();
+ mTertiaryKey = generateAesKey();
+ // In good situations it's always the same. We allow changing it for testing when somehow it
+ // has become mismatched that we throw an error.
+ mChunkEncryptionKey = mTertiaryKey;
+ mInputFile = mTemporaryFolder.newFile();
+ mOutputFile = mTemporaryFolder.newFile();
+ mFileOutput = new DecryptedChunkFileOutput(mOutputFile);
+ mKvOutput = new DecryptedChunkKvOutput(new ChunkHasher(mTertiaryKey));
+ mTask = new BackupFileDecryptorTask(mTertiaryKey);
+ }
+
+ @Test
+ public void decryptFile_throwsForNonExistentInput() throws Exception {
+ assertThrows(
+ FileNotFoundException.class,
+ () ->
+ mTask.decryptFile(
+ new File(mTemporaryFolder.newFolder(), "nonexistent"),
+ mFileOutput));
+ }
+
+ @Test
+ public void decryptFile_throwsForDirectoryInputFile() throws Exception {
+ assertThrows(
+ FileNotFoundException.class,
+ () -> mTask.decryptFile(mTemporaryFolder.newFolder(), mFileOutput));
+ }
+
+ @Test
+ public void decryptFile_withExplicitStarts_decryptsEncryptedData() throws Exception {
+ byte[] backupData = randomData(BACKUP_DATA_SIZE_BYTES);
+ createEncryptedFileUsingExplicitStarts(backupData);
+
+ mTask.decryptFile(mInputFile, mFileOutput);
+
+ assertThat(Files.readAllBytes(Paths.get(mOutputFile.toURI()))).isEqualTo(backupData);
+ }
+
+ @Test
+ public void decryptFile_withInlineLengths_decryptsEncryptedData() throws Exception {
+ createEncryptedFileUsingInlineLengths(
+ TEST_KV_DATA, chunkOrdering -> chunkOrdering, chunksMetadata -> chunksMetadata);
+ mTask.decryptFile(mInputFile, mKvOutput);
+ assertThat(asMap(mKvOutput.getPairs())).containsExactlyEntriesIn(asMap(TEST_KV_DATA));
+ }
+
+ @Test
+ public void decryptFile_withNoChunkOrderingType_decryptsUsingExplicitStarts() throws Exception {
+ byte[] backupData = randomData(BACKUP_DATA_SIZE_BYTES);
+ createEncryptedFileUsingExplicitStarts(
+ backupData,
+ chunkOrdering -> chunkOrdering,
+ chunksMetadata -> {
+ ChunksMetadata metadata = CryptoTestUtils.clone(chunksMetadata);
+ metadata.chunkOrderingType =
+ ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED;
+ return metadata;
+ });
+
+ mTask.decryptFile(mInputFile, mFileOutput);
+
+ assertThat(Files.readAllBytes(Paths.get(mOutputFile.toURI()))).isEqualTo(backupData);
+ }
+
+ @Test
+ public void decryptFile_withInlineLengths_throwsForZeroLengths() throws Exception {
+ createEncryptedFileUsingInlineLengths(
+ TEST_KV_DATA, chunkOrdering -> chunkOrdering, chunksMetadata -> chunksMetadata);
+
+ // Set the length of the first chunk to zero.
+ RandomAccessFile raf = new RandomAccessFile(mInputFile, READ_WRITE_MODE);
+ raf.seek(0);
+ raf.writeInt(0);
+
+ assertThrows(
+ MalformedEncryptedFileException.class,
+ () -> mTask.decryptFile(mInputFile, mKvOutput));
+ }
+
+ @Test
+ public void decryptFile_withInlineLengths_throwsForLongLengths() throws Exception {
+ createEncryptedFileUsingInlineLengths(
+ TEST_KV_DATA, chunkOrdering -> chunkOrdering, chunksMetadata -> chunksMetadata);
+
+ // Set the length of the first chunk to zero.
+ RandomAccessFile raf = new RandomAccessFile(mInputFile, READ_WRITE_MODE);
+ raf.seek(0);
+ raf.writeInt((int) mInputFile.length());
+
+ assertThrows(
+ MalformedEncryptedFileException.class,
+ () -> mTask.decryptFile(mInputFile, mKvOutput));
+ }
+
+ @Test
+ public void decryptFile_throwsForBadKey() throws Exception {
+ createEncryptedFileUsingExplicitStarts(randomData(BACKUP_DATA_SIZE_BYTES));
+
+ assertThrows(
+ AEADBadTagException.class,
+ () ->
+ new BackupFileDecryptorTask(generateAesKey())
+ .decryptFile(mInputFile, mFileOutput));
+ }
+
+ @Test
+ public void decryptFile_withExplicitStarts_throwsForMangledOrdering() throws Exception {
+ createEncryptedFileUsingExplicitStarts(
+ randomData(BACKUP_DATA_SIZE_BYTES),
+ chunkOrdering -> {
+ ChunkOrdering ordering = CryptoTestUtils.clone(chunkOrdering);
+ Arrays.sort(ordering.starts);
+ return ordering;
+ });
+
+ assertThrows(
+ MessageDigestMismatchException.class,
+ () -> mTask.decryptFile(mInputFile, mFileOutput));
+ }
+
+ @Test
+ public void decryptFile_withExplicitStarts_noChunks_returnsNoData() throws Exception {
+ byte[] backupData = randomData(/*length=*/ 0);
+ createEncryptedFileUsingExplicitStarts(
+ backupData,
+ chunkOrdering -> {
+ ChunkOrdering ordering = CryptoTestUtils.clone(chunkOrdering);
+ ordering.starts = new int[0];
+ return ordering;
+ });
+
+ mTask.decryptFile(mInputFile, mFileOutput);
+
+ assertThat(Files.readAllBytes(Paths.get(mOutputFile.toURI()))).isEqualTo(backupData);
+ }
+
+ @Test
+ public void decryptFile_throwsForMismatchedChecksum() throws Exception {
+ createEncryptedFileUsingExplicitStarts(
+ randomData(BACKUP_DATA_SIZE_BYTES),
+ chunkOrdering -> {
+ ChunkOrdering ordering = CryptoTestUtils.clone(chunkOrdering);
+ ordering.checksum =
+ Arrays.copyOf(randomData(CHECKSUM_LENGTH_BYTES), CHECKSUM_LENGTH_BYTES);
+ return ordering;
+ });
+
+ assertThrows(
+ MessageDigestMismatchException.class,
+ () -> mTask.decryptFile(mInputFile, mFileOutput));
+ }
+
+ @Test
+ public void decryptFile_throwsForBadChunksMetadataOffset() throws Exception {
+ createEncryptedFileUsingExplicitStarts(randomData(BACKUP_DATA_SIZE_BYTES));
+
+ // Replace the metadata with all 1s.
+ RandomAccessFile raf = new RandomAccessFile(mInputFile, READ_WRITE_MODE);
+ raf.seek(raf.length() - Long.BYTES);
+ int metadataOffset = (int) raf.readLong();
+ int metadataLength = (int) raf.length() - metadataOffset - Long.BYTES;
+
+ byte[] allOnes = new byte[metadataLength];
+ Arrays.fill(allOnes, (byte) 1);
+
+ raf.seek(metadataOffset);
+ raf.write(allOnes, /*off=*/ 0, metadataLength);
+
+ MalformedEncryptedFileException thrown =
+ expectThrows(
+ MalformedEncryptedFileException.class,
+ () -> mTask.decryptFile(mInputFile, mFileOutput));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(
+ "Could not read chunks metadata at position "
+ + metadataOffset
+ + " of file of "
+ + raf.length()
+ + " bytes");
+ }
+
+ @Test
+ public void decryptFile_throwsForChunksMetadataOffsetBeyondEndOfFile() throws Exception {
+ createEncryptedFileUsingExplicitStarts(randomData(BACKUP_DATA_SIZE_BYTES));
+
+ RandomAccessFile raf = new RandomAccessFile(mInputFile, READ_WRITE_MODE);
+ raf.seek(raf.length() - Long.BYTES);
+ raf.writeLong(raf.length());
+
+ MalformedEncryptedFileException thrown =
+ expectThrows(
+ MalformedEncryptedFileException.class,
+ () -> mTask.decryptFile(mInputFile, mFileOutput));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(
+ raf.length()
+ + " is not valid position for chunks metadata in file of "
+ + raf.length()
+ + " bytes");
+ }
+
+ @Test
+ public void decryptFile_throwsForChunksMetadataOffsetBeforeBeginningOfFile() throws Exception {
+ createEncryptedFileUsingExplicitStarts(randomData(BACKUP_DATA_SIZE_BYTES));
+
+ RandomAccessFile raf = new RandomAccessFile(mInputFile, READ_WRITE_MODE);
+ raf.seek(raf.length() - Long.BYTES);
+ raf.writeLong(-1);
+
+ MalformedEncryptedFileException thrown =
+ expectThrows(
+ MalformedEncryptedFileException.class,
+ () -> mTask.decryptFile(mInputFile, mFileOutput));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(
+ "-1 is not valid position for chunks metadata in file of "
+ + raf.length()
+ + " bytes");
+ }
+
+ @Test
+ public void decryptFile_throwsForMangledChunks() throws Exception {
+ createEncryptedFileUsingExplicitStarts(randomData(BACKUP_DATA_SIZE_BYTES));
+
+ // Mess up some bits in a random byte
+ RandomAccessFile raf = new RandomAccessFile(mInputFile, READ_WRITE_MODE);
+ raf.seek(50);
+ byte fiftiethByte = raf.readByte();
+ raf.seek(50);
+ raf.write(~fiftiethByte);
+
+ assertThrows(AEADBadTagException.class, () -> mTask.decryptFile(mInputFile, mFileOutput));
+ }
+
+ @Test
+ public void decryptFile_throwsForBadChunkEncryptionKey() throws Exception {
+ mChunkEncryptionKey = generateAesKey();
+
+ createEncryptedFileUsingExplicitStarts(randomData(BACKUP_DATA_SIZE_BYTES));
+
+ assertThrows(AEADBadTagException.class, () -> mTask.decryptFile(mInputFile, mFileOutput));
+ }
+
+ @Test
+ public void decryptFile_throwsForUnsupportedCipherType() throws Exception {
+ createEncryptedFileUsingExplicitStarts(
+ randomData(BACKUP_DATA_SIZE_BYTES),
+ chunkOrdering -> chunkOrdering,
+ chunksMetadata -> {
+ ChunksMetadata metadata = CryptoTestUtils.clone(chunksMetadata);
+ metadata.cipherType = ChunksMetadataProto.UNKNOWN_CIPHER_TYPE;
+ return metadata;
+ });
+
+ assertThrows(
+ UnsupportedEncryptedFileException.class,
+ () -> mTask.decryptFile(mInputFile, mFileOutput));
+ }
+
+ @Test
+ public void decryptFile_throwsForUnsupportedMessageDigestType() throws Exception {
+ createEncryptedFileUsingExplicitStarts(
+ randomData(BACKUP_DATA_SIZE_BYTES),
+ chunkOrdering -> chunkOrdering,
+ chunksMetadata -> {
+ ChunksMetadata metadata = CryptoTestUtils.clone(chunksMetadata);
+ metadata.checksumType = ChunksMetadataProto.UNKNOWN_CHECKSUM_TYPE;
+ return metadata;
+ });
+
+ assertThrows(
+ UnsupportedEncryptedFileException.class,
+ () -> mTask.decryptFile(mInputFile, mFileOutput));
+ }
+
+ /**
+ * Creates an encrypted backup file from the given data.
+ *
+ * @param data The plaintext content.
+ */
+ private void createEncryptedFileUsingExplicitStarts(byte[] data) throws Exception {
+ createEncryptedFileUsingExplicitStarts(data, chunkOrdering -> chunkOrdering);
+ }
+
+ /**
+ * Creates an encrypted backup file from the given data.
+ *
+ * @param data The plaintext content.
+ * @param chunkOrderingTransformer Transforms the ordering before it's encrypted.
+ */
+ private void createEncryptedFileUsingExplicitStarts(
+ byte[] data, Transformer<ChunkOrdering> chunkOrderingTransformer) throws Exception {
+ createEncryptedFileUsingExplicitStarts(
+ data, chunkOrderingTransformer, chunksMetadata -> chunksMetadata);
+ }
+
+ /**
+ * Creates an encrypted backup file from the given data in mode {@link
+ * ChunksMetadataProto#EXPLICIT_STARTS}.
+ *
+ * @param data The plaintext content.
+ * @param chunkOrderingTransformer Transforms the ordering before it's encrypted.
+ * @param chunksMetadataTransformer Transforms the metadata before it's written.
+ */
+ private void createEncryptedFileUsingExplicitStarts(
+ byte[] data,
+ Transformer<ChunkOrdering> chunkOrderingTransformer,
+ Transformer<ChunksMetadata> chunksMetadataTransformer)
+ throws Exception {
+ Result result = backupFullData(data);
+
+ ArrayList<EncryptedChunk> chunks = new ArrayList<>(result.getNewChunks());
+ Collections.shuffle(chunks);
+ HashMap<ChunkHash, Integer> startPositions = new HashMap<>();
+
+ try (FileOutputStream fos = new FileOutputStream(mInputFile);
+ DataOutputStream dos = new DataOutputStream(fos)) {
+ int position = 0;
+
+ for (EncryptedChunk chunk : chunks) {
+ startPositions.put(chunk.key(), position);
+ dos.write(chunk.nonce());
+ dos.write(chunk.encryptedBytes());
+ position += chunk.nonce().length + chunk.encryptedBytes().length;
+ }
+
+ int[] starts = new int[chunks.size()];
+ List<ChunkHash> chunkListing = result.getAllChunks();
+
+ for (int i = 0; i < chunks.size(); i++) {
+ starts[i] = startPositions.get(chunkListing.get(i));
+ }
+
+ ChunkOrdering chunkOrdering = newChunkOrdering(starts, result.getDigest());
+ chunkOrdering = chunkOrderingTransformer.accept(chunkOrdering);
+
+ ChunksMetadata metadata =
+ newChunksMetadata(
+ ChunksMetadataProto.AES_256_GCM,
+ ChunksMetadataProto.SHA_256,
+ ChunksMetadataProto.EXPLICIT_STARTS,
+ encrypt(chunkOrdering));
+ metadata = chunksMetadataTransformer.accept(metadata);
+
+ dos.write(MessageNano.toByteArray(metadata));
+ dos.writeLong(position);
+ }
+ }
+
+ /**
+ * Creates an encrypted backup file from the given data in mode {@link
+ * ChunksMetadataProto#INLINE_LENGTHS}.
+ *
+ * @param data The plaintext key value pairs to back up.
+ * @param chunkOrderingTransformer Transforms the ordering before it's encrypted.
+ * @param chunksMetadataTransformer Transforms the metadata before it's written.
+ */
+ private void createEncryptedFileUsingInlineLengths(
+ Set<KeyValuePair> data,
+ Transformer<ChunkOrdering> chunkOrderingTransformer,
+ Transformer<ChunksMetadata> chunksMetadataTransformer)
+ throws Exception {
+ Result result = backupKvData(data);
+
+ List<EncryptedChunk> chunks = new ArrayList<>(result.getNewChunks());
+ System.out.println("we have chunk count " + chunks.size());
+ Collections.shuffle(chunks);
+
+ try (FileOutputStream fos = new FileOutputStream(mInputFile);
+ DataOutputStream dos = new DataOutputStream(fos)) {
+ for (EncryptedChunk chunk : chunks) {
+ dos.writeInt(chunk.nonce().length + chunk.encryptedBytes().length);
+ dos.write(chunk.nonce());
+ dos.write(chunk.encryptedBytes());
+ }
+
+ ChunkOrdering chunkOrdering = newChunkOrdering(null, result.getDigest());
+ chunkOrdering = chunkOrderingTransformer.accept(chunkOrdering);
+
+ ChunksMetadata metadata =
+ newChunksMetadata(
+ ChunksMetadataProto.AES_256_GCM,
+ ChunksMetadataProto.SHA_256,
+ ChunksMetadataProto.INLINE_LENGTHS,
+ encrypt(chunkOrdering));
+ metadata = chunksMetadataTransformer.accept(metadata);
+
+ int metadataStart = dos.size();
+ dos.write(MessageNano.toByteArray(metadata));
+ dos.writeLong(metadataStart);
+ }
+ }
+
+ /** Performs a full backup of the given data, and returns the chunks. */
+ private BackupEncrypter.Result backupFullData(byte[] data) throws Exception {
+ BackupStreamEncrypter encrypter =
+ new BackupStreamEncrypter(
+ new ByteArrayInputStream(data),
+ MIN_CHUNK_SIZE_BYTES,
+ MAX_CHUNK_SIZE_BYTES,
+ AVERAGE_CHUNK_SIZE_BYTES);
+ return encrypter.backup(
+ mChunkEncryptionKey,
+ randomData(FingerprintMixer.SALT_LENGTH_BYTES),
+ new HashSet<>());
+ }
+
+ private Result backupKvData(Set<KeyValuePair> data) throws Exception {
+ ShadowBackupDataInput.reset();
+ for (KeyValuePair pair : data) {
+ ShadowBackupDataInput.addEntity(pair.key, pair.value);
+ }
+ KvBackupEncrypter encrypter =
+ new KvBackupEncrypter(new BackupDataInput(NULL_FILE_DESCRIPTOR));
+ return encrypter.backup(
+ mChunkEncryptionKey,
+ randomData(FingerprintMixer.SALT_LENGTH_BYTES),
+ Collections.EMPTY_SET);
+ }
+
+ /** Encrypts {@code chunkOrdering} using {@link #mTertiaryKey}. */
+ private byte[] encrypt(ChunkOrdering chunkOrdering) throws Exception {
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+ byte[] nonce = randomData(GCM_NONCE_LENGTH_BYTES);
+ cipher.init(
+ Cipher.ENCRYPT_MODE,
+ mTertiaryKey,
+ new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE, nonce));
+ byte[] nanoBytes = MessageNano.toByteArray(chunkOrdering);
+ byte[] encryptedBytes = cipher.doFinal(nanoBytes);
+
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ out.write(nonce);
+ out.write(encryptedBytes);
+ return out.toByteArray();
+ }
+ }
+
+ /** Returns {@code length} random bytes. */
+ private byte[] randomData(int length) {
+ byte[] data = new byte[length];
+ mRandom.nextBytes(data);
+ return data;
+ }
+
+ private static ImmutableMap<String, String> asMap(Collection<KeyValuePair> pairs) {
+ ImmutableMap.Builder<String, String> map = ImmutableMap.builder();
+ for (KeyValuePair pair : pairs) {
+ map.put(pair.key, new String(pair.value, Charset.forName("UTF-8")));
+ }
+ return map.build();
+ }
+
+ private interface Transformer<T> {
+ T accept(T t);
+ }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTaskTest.java
new file mode 100644
index 000000000000..de8b7340ebce
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTaskTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 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.tasks;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.FullRestoreDownloader;
+import com.android.server.backup.encryption.FullRestoreDownloader.FinishType;
+
+import com.google.common.io.Files;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.Random;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class FullRestoreToFileTaskTest {
+ private static final int TEST_RANDOM_SEED = 34;
+ private static final int TEST_MAX_CHUNK_SIZE_BYTES = 5;
+ private static final int TEST_DATA_LENGTH_BYTES = TEST_MAX_CHUNK_SIZE_BYTES * 20;
+
+ @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ private byte[] mTestData;
+ private File mTargetFile;
+ private FakeFullRestoreDownloader mFakeFullRestoreDownloader;
+ @Mock private FullRestoreDownloader mMockFullRestoreDownloader;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mTargetFile = mTemporaryFolder.newFile();
+
+ mTestData = new byte[TEST_DATA_LENGTH_BYTES];
+ new Random(TEST_RANDOM_SEED).nextBytes(mTestData);
+ mFakeFullRestoreDownloader = new FakeFullRestoreDownloader(mTestData);
+ }
+
+ private FullRestoreToFileTask createTaskWithFakeDownloader() {
+ return new FullRestoreToFileTask(mFakeFullRestoreDownloader, TEST_MAX_CHUNK_SIZE_BYTES);
+ }
+
+ private FullRestoreToFileTask createTaskWithMockDownloader() {
+ return new FullRestoreToFileTask(mMockFullRestoreDownloader, TEST_MAX_CHUNK_SIZE_BYTES);
+ }
+
+ @Test
+ public void restoreToFile_readsDataAndWritesToFile() throws Exception {
+ FullRestoreToFileTask task = createTaskWithFakeDownloader();
+ task.restoreToFile(mTargetFile);
+ assertThat(Files.toByteArray(mTargetFile)).isEqualTo(mTestData);
+ }
+
+ @Test
+ public void restoreToFile_noErrors_closesDownloaderWithFinished() throws Exception {
+ FullRestoreToFileTask task = createTaskWithMockDownloader();
+ when(mMockFullRestoreDownloader.readNextChunk(any())).thenReturn(-1);
+
+ task.restoreToFile(mTargetFile);
+
+ verify(mMockFullRestoreDownloader).finish(FinishType.FINISHED);
+ }
+
+ @Test
+ public void restoreToFile_ioException_closesDownloaderWithTransferFailure() throws Exception {
+ FullRestoreToFileTask task = createTaskWithMockDownloader();
+ when(mMockFullRestoreDownloader.readNextChunk(any())).thenThrow(IOException.class);
+
+ assertThrows(IOException.class, () -> task.restoreToFile(mTargetFile));
+
+ verify(mMockFullRestoreDownloader).finish(FinishType.TRANSFER_FAILURE);
+ }
+
+ /** Fake package wrapper which returns data from a byte array. */
+ private static class FakeFullRestoreDownloader extends FullRestoreDownloader {
+
+ private final ByteArrayInputStream mData;
+
+ FakeFullRestoreDownloader(byte[] data) {
+ // We override all methods of the superclass, so it does not require any collaborators.
+ super();
+ this.mData = new ByteArrayInputStream(data);
+ }
+
+ @Override
+ public int readNextChunk(byte[] buffer) throws IOException {
+ return mData.read(buffer);
+ }
+
+ @Override
+ public void finish(FinishType finishType) {
+ // Do nothing.
+ }
+ }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java
index b9055cecd502..5cff53f817d4 100644
--- a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java
@@ -18,7 +18,9 @@ package com.android.server.backup.testing;
import com.android.server.backup.encryption.chunk.ChunkHash;
import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.KeyValuePairProto;
+import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Random;
@@ -86,11 +88,33 @@ public class CryptoTestUtils {
public static ChunksMetadataProto.ChunkOrdering newChunkOrdering(
int[] starts, byte[] checksum) {
ChunksMetadataProto.ChunkOrdering chunkOrdering = new ChunksMetadataProto.ChunkOrdering();
- chunkOrdering.starts = Arrays.copyOf(starts, starts.length);
- chunkOrdering.checksum = Arrays.copyOf(checksum, checksum.length);
+ chunkOrdering.starts = starts == null ? null : Arrays.copyOf(starts, starts.length);
+ chunkOrdering.checksum =
+ checksum == null ? checksum : Arrays.copyOf(checksum, checksum.length);
return chunkOrdering;
}
+ public static ChunksMetadataProto.ChunksMetadata newChunksMetadata(
+ int cipherType, int checksumType, int chunkOrderingType, byte[] chunkOrdering) {
+ ChunksMetadataProto.ChunksMetadata metadata = new ChunksMetadataProto.ChunksMetadata();
+ metadata.cipherType = cipherType;
+ metadata.checksumType = checksumType;
+ metadata.chunkOrdering = Arrays.copyOf(chunkOrdering, chunkOrdering.length);
+ metadata.chunkOrderingType = chunkOrderingType;
+ return metadata;
+ }
+
+ public static KeyValuePairProto.KeyValuePair newPair(String key, String value) {
+ return newPair(key, value.getBytes(Charset.forName("UTF-8")));
+ }
+
+ public static KeyValuePairProto.KeyValuePair newPair(String key, byte[] value) {
+ KeyValuePairProto.KeyValuePair newPair = new KeyValuePairProto.KeyValuePair();
+ newPair.key = key;
+ newPair.value = value;
+ return newPair;
+ }
+
public static ChunksMetadataProto.ChunkListing clone(
ChunksMetadataProto.ChunkListing original) {
ChunksMetadataProto.Chunk[] clonedChunks;
@@ -114,4 +138,25 @@ public class CryptoTestUtils {
public static ChunksMetadataProto.Chunk clone(ChunksMetadataProto.Chunk original) {
return newChunk(original.hash, original.length);
}
+
+ public static ChunksMetadataProto.ChunksMetadata clone(
+ ChunksMetadataProto.ChunksMetadata original) {
+ ChunksMetadataProto.ChunksMetadata cloneMetadata = new ChunksMetadataProto.ChunksMetadata();
+ cloneMetadata.chunkOrderingType = original.chunkOrderingType;
+ cloneMetadata.chunkOrdering =
+ original.chunkOrdering == null
+ ? null
+ : Arrays.copyOf(original.chunkOrdering, original.chunkOrdering.length);
+ cloneMetadata.checksumType = original.checksumType;
+ cloneMetadata.cipherType = original.cipherType;
+ return cloneMetadata;
+ }
+
+ public static ChunksMetadataProto.ChunkOrdering clone(
+ ChunksMetadataProto.ChunkOrdering original) {
+ ChunksMetadataProto.ChunkOrdering clone = new ChunksMetadataProto.ChunkOrdering();
+ clone.starts = Arrays.copyOf(original.starts, original.starts.length);
+ clone.checksum = Arrays.copyOf(original.checksum, original.checksum.length);
+ return clone;
+ }
}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/TestFileUtils.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/TestFileUtils.java
new file mode 100644
index 000000000000..e5d73ba41902
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/TestFileUtils.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+import com.google.common.io.ByteStreams;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/** Utility methods for use in tests */
+public class TestFileUtils {
+ /** Read the contents of a file into a byte array */
+ public static byte[] toByteArray(File file) throws IOException {
+ try (FileInputStream fis = new FileInputStream(file)) {
+ return ByteStreams.toByteArray(fis);
+ }
+ }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 8f474704097e..1f923aff0cea 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1004,9 +1004,9 @@
<!-- [CHAR_LIMIT=40] Label for battery level chart when charging -->
<string name="power_charging"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="state">%2$s</xliff:g></string>
<!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging -->
- <string name="power_remaining_charging_duration_only"><xliff:g id="time">%1$s</xliff:g> left until fully charged</string>
+ <string name="power_remaining_charging_duration_only"><xliff:g id="time">%1$s</xliff:g> left until charged</string>
<!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
- <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> until fully charged</string>
+ <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> until charged</string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_unknown">Unknown</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 6a906719d3fa..19c666459723 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -865,6 +865,10 @@ public class ApplicationsState {
void handleRebuildList() {
AppFilter filter;
Comparator<AppEntry> comparator;
+
+ if (!mResumed) {
+ return;
+ }
synchronized (mRebuildSync) {
if (!mRebuildRequested) {
return;
@@ -1069,8 +1073,8 @@ public class ApplicationsState {
}
}
if (rebuildingSessions != null) {
- for (int i = 0; i < rebuildingSessions.size(); i++) {
- rebuildingSessions.get(i).handleRebuildList();
+ for (Session session : rebuildingSessions) {
+ session.handleRebuildList();
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java b/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java
index 5c9a06f91e6a..4e052f1aeca4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java
@@ -26,7 +26,7 @@ public class ThreadUtils {
private static volatile Thread sMainThread;
private static volatile Handler sMainThreadHandler;
- private static volatile ExecutorService sSingleThreadExecutor;
+ private static volatile ExecutorService sThreadExecutor;
/**
* Returns true if the current thread is the UI thread.
@@ -64,10 +64,11 @@ public class ThreadUtils {
* @Return A future of the task that can be monitored for updates or cancelled.
*/
public static Future postOnBackgroundThread(Runnable runnable) {
- if (sSingleThreadExecutor == null) {
- sSingleThreadExecutor = Executors.newSingleThreadExecutor();
+ if (sThreadExecutor == null) {
+ sThreadExecutor = Executors.newFixedThreadPool(
+ Runtime.getRuntime().availableProcessors());
}
- return sSingleThreadExecutor.submit(runnable);
+ return sThreadExecutor.submit(runnable);
}
/**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index f8697a19c7ab..95a4f69b287c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -269,7 +269,7 @@ public class ApplicationsStateRoboTest {
}
@Test
- public void testDefaultSessionLoadsAll() {
+ public void testDefaultSession_isResumed_LoadsAll() {
mSession.onResume();
addApp(HOME_PACKAGE_NAME, 1);
@@ -296,6 +296,19 @@ public class ApplicationsStateRoboTest {
}
@Test
+ public void testDefaultSession_isPaused_NotLoadsAll() {
+ mSession.onResume();
+
+ addApp(HOME_PACKAGE_NAME, 1);
+ addApp(LAUNCHABLE_PACKAGE_NAME, 2);
+ mSession.mResumed = false;
+ mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+ processAllMessages();
+
+ verify(mCallbacks, never()).onRebuildComplete(mAppEntriesCaptor.capture());
+ }
+
+ @Test
public void testCustomSessionLoadsIconsOnly() {
mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS);
mSession.onResume();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java
index 26db124c0041..5114b00d2711 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java
@@ -50,7 +50,7 @@ public class ThreadUtilsTest {
}
@Test
- public void testPostOnMainThread_shouldRunOnMainTread() {
+ public void testPostOnMainThread_shouldRunOnMainThread() {
TestRunnable cr = new TestRunnable();
ShadowLooper.pauseMainLooper();
ThreadUtils.postOnMainThread(cr);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 3a7de188f962..86625faab4d6 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -719,7 +719,8 @@ public class SettingsBackupTest {
Settings.Secure.BIOMETRIC_DEBUG_ENABLED,
Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED,
Settings.Secure.FACE_UNLOCK_DIVERSITY_REQUIRED,
- Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED);
+ Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED,
+ Settings.Secure.FACE_UNLOCK_RE_ENROLL);
@Test
public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index e767bccc7b4a..c1cc2616511b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -240,7 +240,7 @@
<activity
android:name=".BugreportWarningActivity"
- android:theme="@android:style/Theme.DeviceDefault.Dialog.Alert"
+ android:theme="@*android:style/Theme.DeviceDefault.Dialog.Alert.DayNight"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true"
android:exported="false" />
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 0fe7084bb145..485240a8895b 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -62,7 +62,7 @@
<!-- When the lock screen is showing, the phone is plugged in and the battery is fully
charged, say that it is charged. -->
- <string name="keyguard_charged">Fully charged</string>
+ <string name="keyguard_charged">Charged</string>
<!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's wirelessly charging. [CHAR LIMIT=50] -->
<string name="keyguard_plugged_in_wireless"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging wirelessly</string>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_plugin_frame.xml b/packages/SystemUI/res/layout/status_bar_expanded_plugin_frame.xml
index 4849dfb777ed..7d6ff3b16db6 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded_plugin_frame.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded_plugin_frame.xml
@@ -20,10 +20,10 @@
android:id="@+id/plugin_frame"
android:theme="@style/qs_theme"
android:layout_width="@dimen/qs_panel_width"
- android:layout_height="96dp"
+ android:layout_height="105dp"
android:layout_gravity="center_horizontal"
- android:layout_marginTop="@*android:dimen/quick_qs_total_height"
+ android:layout_marginTop="@dimen/notification_side_paddings"
android:layout_marginLeft="@dimen/notification_side_paddings"
android:layout_marginRight="@dimen/notification_side_paddings"
android:visibility="gone"
- android:background="@drawable/qs_background_primary"/> \ No newline at end of file
+ android:background="@drawable/qs_background_primary"/>
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
index 2c8324cafca0..aa13fa834f56 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@@ -92,6 +92,12 @@ public class SystemUIAppComponentFactory extends AppComponentFactory {
public Activity instantiateActivityCompat(@NonNull ClassLoader cl, @NonNull String className,
@Nullable Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+ if (mComponentHelper == null) {
+ // This shouldn't happen, but is seen on occasion.
+ // Bug filed against framework to take a look: http://b/141008541
+ SystemUIFactory.getInstance().getRootComponent().inject(
+ SystemUIAppComponentFactory.this);
+ }
Activity activity = mComponentHelper.resolveActivity(className);
if (activity != null) {
return activity;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
index 4531c892a022..0fa80aca97fb 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
@@ -17,6 +17,7 @@
package com.android.systemui;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.power.PowerUI;
import dagger.Binds;
import dagger.Module;
@@ -33,4 +34,10 @@ public abstract class SystemUIBinder {
@IntoMap
@ClassKey(KeyguardViewMediator.class)
public abstract SystemUI bindKeyguardViewMediator(KeyguardViewMediator sysui);
+
+ /** Inject into PowerUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(PowerUI.class)
+ public abstract SystemUI bindPowerUI(PowerUI sysui);
}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index f0e8c16e650a..5e977b4684dc 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -75,7 +75,7 @@ open class BroadcastDispatcher @Inject constructor (
* @param filter A filter to determine what broadcasts should be dispatched to this receiver.
* It will only take into account actions and categories for filtering.
* @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the
- * main handler.
+ * main handler. Pass `null` to use the default.
* @param user A user handle to determine which broadcast should be dispatched to this receiver.
* By default, it is the current user.
*/
@@ -83,10 +83,12 @@ open class BroadcastDispatcher @Inject constructor (
fun registerReceiver(
receiver: BroadcastReceiver,
filter: IntentFilter,
- handler: Handler = mainHandler,
+ handler: Handler? = mainHandler,
user: UserHandle = context.user
) {
- this.handler.obtainMessage(MSG_ADD_RECEIVER, ReceiverData(receiver, filter, handler, user))
+ this.handler
+ .obtainMessage(MSG_ADD_RECEIVER,
+ ReceiverData(receiver, filter, handler ?: mainHandler, user))
.sendToTarget()
}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index d44b63e813e6..54f9950239c2 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -34,7 +34,7 @@ import java.util.concurrent.atomic.AtomicBoolean
private const val MSG_REGISTER_RECEIVER = 0
private const val MSG_UNREGISTER_RECEIVER = 1
-private const val TAG = "UniversalReceiver"
+private const val TAG = "UserBroadcastDispatcher"
private const val DEBUG = false
/**
@@ -97,7 +97,7 @@ class UserBroadcastDispatcher(
private val receiverToReceiverData = ArrayMap<BroadcastReceiver, MutableSet<ReceiverData>>()
override fun onReceive(context: Context, intent: Intent) {
- bgHandler.post(HandleBroadcastRunnable(actionsToReceivers, context, intent))
+ bgHandler.post(HandleBroadcastRunnable(actionsToReceivers, context, intent, pendingResult))
}
/**
@@ -160,7 +160,8 @@ class UserBroadcastDispatcher(
private class HandleBroadcastRunnable(
val actionsToReceivers: Map<String, Set<ReceiverData>>,
val context: Context,
- val intent: Intent
+ val intent: Intent,
+ val pendingResult: PendingResult
) : Runnable {
override fun run() {
if (DEBUG) Log.w(TAG, "Dispatching $intent")
@@ -171,6 +172,7 @@ class UserBroadcastDispatcher(
?.forEach {
it.handler.post {
if (DEBUG) Log.w(TAG, "Dispatching to ${it.receiver}")
+ it.receiver.pendingResult = pendingResult
it.receiver.onReceive(context, intent)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 57a5ae63511c..22846bc02a38 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -90,6 +90,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.MultiListLayout;
import com.android.systemui.MultiListLayout.MultiListAdapter;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
@@ -187,7 +188,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
- context.registerReceiver(mBroadcastReceiver, filter);
+ Dependency.get(BroadcastDispatcher.class).registerReceiver(mBroadcastReceiver, filter);
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 4f2a6d82a08e..5723afd4ae95 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -247,6 +247,9 @@ public class PipMenuActivity extends Activity {
protected void onStop() {
super.onStop();
+ // In cases such as device lock, hide and finish it so that it can be recreated on the top
+ // next time it starts, see also {@link #onUserLeaveHint}
+ hideMenu();
cancelDelayedFinish();
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 75dc39722bcf..a258f356bf53 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -45,6 +45,7 @@ import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.statusbar.phone.StatusBar;
import java.io.FileDescriptor;
@@ -53,6 +54,8 @@ import java.time.Duration;
import java.util.Arrays;
import java.util.concurrent.Future;
+import javax.inject.Inject;
+
public class PowerUI extends SystemUI {
static final String TAG = "PowerUI";
@@ -97,6 +100,12 @@ public class PowerUI extends SystemUI {
private IThermalEventListener mSkinThermalEventListener;
private IThermalEventListener mUsbThermalEventListener;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+
+ @Inject
+ public PowerUI(BroadcastDispatcher broadcastDispatcher) {
+ mBroadcastDispatcher = broadcastDispatcher;
+ }
public void start() {
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -211,7 +220,7 @@ public class PowerUI extends SystemUI {
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_USER_SWITCHED);
- mContext.registerReceiver(this, filter, null, mHandler);
+ mBroadcastDispatcher.registerReceiver(this, filter, mHandler);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index d20b22815805..4013586d4197 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -257,10 +257,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mNextAlarmTextView.setSelected(true);
mPermissionsHubEnabled = PrivacyItemControllerKt.isPermissionsHubEnabled();
- // Change the ignored slots when DeviceConfig flag changes
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY,
- mContext.getMainExecutor(), mPropertiesListener);
-
}
private List<String> getIgnoredIconSlots() {
@@ -489,6 +485,9 @@ public class QuickStatusBarHeader extends RelativeLayout implements
super.onAttachedToWindow();
mStatusBarIconController.addIconGroup(mIconManager);
requestApplyInsets();
+ // Change the ignored slots when DeviceConfig flag changes
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY,
+ mContext.getMainExecutor(), mPropertiesListener);
}
@Override
@@ -527,6 +526,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
public void onDetachedFromWindow() {
setListening(false);
mStatusBarIconController.removeIconGroup(mIconManager);
+ DeviceConfig.removeOnPropertiesChangedListener(mPropertiesListener);
super.onDetachedFromWindow();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index d0c47345a83a..c1ce16337f8d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -61,8 +61,10 @@ import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
@@ -245,10 +247,15 @@ public class RecentsOnboarding {
private final View.OnAttachStateChangeListener mOnAttachStateChangeListener
= new View.OnAttachStateChangeListener() {
+
+ private final BroadcastDispatcher mBroadcastDispatcher = Dependency.get(
+ BroadcastDispatcher.class);
+
@Override
public void onViewAttachedToWindow(View view) {
if (view == mLayout) {
- mContext.registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ mBroadcastDispatcher.registerReceiver(mReceiver,
+ new IntentFilter(Intent.ACTION_SCREEN_OFF));
mLayoutAttachedToWindow = true;
if (view.getTag().equals(R.string.recents_swipe_up_onboarding)) {
mHasDismissedSwipeUpTip = false;
@@ -273,7 +280,7 @@ public class RecentsOnboarding {
}
mOverviewOpenedCountSinceQuickScrubTipDismiss = 0;
}
- mContext.unregisterReceiver(mReceiver);
+ mBroadcastDispatcher.unregisterReceiver(mReceiver);
}
}
};
@@ -335,10 +342,11 @@ public class RecentsOnboarding {
private void notifyOnTip(int action, int target) {
try {
IOverviewProxy overviewProxy = mOverviewProxyService.getProxy();
- if(overviewProxy != null) {
+ if (overviewProxy != null) {
overviewProxy.onTip(action, target);
}
- } catch (RemoteException e) {}
+ } catch (RemoteException e) {
+ }
}
public void onNavigationModeChanged(int mode) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index c3c0d63f66c4..0f277ca8b2c6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -47,6 +47,7 @@ import android.widget.TextView;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.statusbar.phone.NavigationBarView;
@@ -159,6 +160,8 @@ public class ScreenPinningRequest implements View.OnClickListener,
private ValueAnimator mColorAnim;
private ViewGroup mLayout;
private boolean mShowCancel;
+ private final BroadcastDispatcher mBroadcastDispatcher =
+ Dependency.get(BroadcastDispatcher.class);
public RequestWindowView(Context context, boolean showCancel) {
super(context);
@@ -212,7 +215,7 @@ public class ScreenPinningRequest implements View.OnClickListener,
IntentFilter filter = new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_SCREEN_OFF);
- mContext.registerReceiver(mReceiver, filter);
+ mBroadcastDispatcher.registerReceiver(mReceiver, filter);
}
private void inflateView(int rotation) {
@@ -313,7 +316,7 @@ public class ScreenPinningRequest implements View.OnClickListener,
@Override
public void onDetachedFromWindow() {
- mContext.unregisterReceiver(mReceiver);
+ mBroadcastDispatcher.unregisterReceiver(mReceiver);
}
protected void onConfigurationChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt
index 7dcc2fcfe2b2..53601babfd56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone
import android.content.Context
import android.view.View
+import android.view.ViewGroup.MarginLayoutParams
import android.widget.FrameLayout
import com.android.systemui.plugins.NPVPlugin
import com.android.systemui.plugins.PluginListener
@@ -36,6 +37,7 @@ class NPVPluginManager(
private var plugin: NPVPlugin? = null
private var animator = createAnimator()
+ private var yOffset = 0f
private fun createAnimator() = TouchAnimator.Builder()
.addFloat(parent, "alpha", 1f, 0f)
@@ -76,7 +78,7 @@ class NPVPluginManager(
}
fun setExpansion(expansion: Float, headerTranslation: Float, heightDiff: Float) {
- parent.setTranslationY(expansion * heightDiff + headerTranslation)
+ parent.setTranslationY(expansion * heightDiff + headerTranslation + yOffset)
if (!expansion.isNaN()) animator.setPosition(expansion)
}
@@ -88,5 +90,13 @@ class NPVPluginManager(
animator = createAnimator()
}
- fun getHeight() = if (plugin != null) parent.height else 0
+ fun getHeight() =
+ if (plugin != null) {
+ parent.height + (parent.getLayoutParams() as MarginLayoutParams).topMargin
+ } else 0
+
+ fun setYOffset(y: Float) {
+ yOffset = y
+ parent.setTranslationY(yOffset)
+ }
}
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 b87140dddec3..6e61d7ceaf6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -89,6 +89,7 @@ import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.assist.AssistHandleViewController;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.model.SysUiState;
@@ -139,6 +140,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
private final MetricsLogger mMetricsLogger;
private final DeviceProvisionedController mDeviceProvisionedController;
private final StatusBarStateController mStatusBarStateController;
+ private final NavigationModeController mNavigationModeController;
protected NavigationBarView mNavigationBarView = null;
@@ -170,6 +172,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
private OverviewProxyService mOverviewProxyService;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+
@VisibleForTesting
public int mDisplayId;
private boolean mIsOnDefaultDisplay;
@@ -251,7 +255,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
AssistManager assistManager, OverviewProxyService overviewProxyService,
NavigationModeController navigationModeController,
StatusBarStateController statusBarStateController,
- SysUiState sysUiFlagsContainer) {
+ SysUiState sysUiFlagsContainer,
+ BroadcastDispatcher broadcastDispatcher) {
mAccessibilityManagerWrapper = accessibilityManagerWrapper;
mDeviceProvisionedController = deviceProvisionedController;
mStatusBarStateController = statusBarStateController;
@@ -260,7 +265,9 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
mSysUiFlagsContainer = sysUiFlagsContainer;
mAssistantAvailable = mAssistManager.getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
mOverviewProxyService = overviewProxyService;
+ mNavigationModeController = navigationModeController;
mNavBarMode = navigationModeController.addListener(this);
+ mBroadcastDispatcher = broadcastDispatcher;
}
// ----- Fragment Lifecycle Callbacks -----
@@ -299,6 +306,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
@Override
public void onDestroy() {
super.onDestroy();
+ mNavigationModeController.removeListener(this);
mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
mContentResolver.unregisterContentObserver(mMagnificationObserver);
mContentResolver.unregisterContentObserver(mAssistContentObserver);
@@ -337,7 +345,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_USER_SWITCHED);
- getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
+ mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, Handler.getMain(),
+ UserHandle.ALL);
notifyNavigationBarScreenOn();
mOverviewProxyService.addCallback(mOverviewProxyListener);
@@ -380,7 +389,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
mNavigationBarView.getLightTransitionsController().destroy(getContext());
}
mOverviewProxyService.removeCallback(mOverviewProxyListener);
- getContext().unregisterReceiver(mBroadcastReceiver);
+ mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
}
@Override
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 fa4812dc4876..a1a47e1305f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -829,7 +829,11 @@ public class NavigationBarView extends FrameLayout implements
mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
getRotateSuggestionButton().onNavigationModeChanged(mNavBarMode);
- mRegionSamplingHelper.start(mSamplingBounds);
+ if (isGesturalMode(mNavBarMode)) {
+ mRegionSamplingHelper.start(mSamplingBounds);
+ } else {
+ mRegionSamplingHelper.stop();
+ }
}
public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 86da10a4b970..353a5381aa14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -652,8 +652,7 @@ public class NotificationPanelView extends PanelView implements
mNotificationStackScroller.setLayoutParams(lp);
}
int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
- int topMargin =
- res.getDimensionPixelOffset(com.android.internal.R.dimen.quick_qs_total_height);
+ int topMargin = sideMargin;
lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
|| lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
@@ -796,6 +795,7 @@ public class NotificationPanelView extends PanelView implements
int oldMaxHeight = mQsMaxExpansionHeight;
if (mQs != null) {
mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
+ mNPVPluginManager.setYOffset(mQsMinExpansionHeight);
mQsMinExpansionHeight += mNPVPluginManager.getHeight();
mQsMaxExpansionHeight = mQs.getDesiredHeight();
mNotificationStackScroller.setMaxTopPadding(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
index c1ff572bb210..1a6b415f87db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
@@ -127,6 +127,11 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
updateSamplingListener();
}
+ void stopAndDestroy() {
+ stop();
+ mSamplingListener.destroy();
+ }
+
@Override
public void onViewAttachedToWindow(View view) {
updateSamplingListener();
@@ -134,9 +139,7 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
@Override
public void onViewDetachedFromWindow(View view) {
- // isAttachedToWindow is only changed after this call to the listeners, so let's post it
- // instead
- postUpdateSamplingListener();
+ stopAndDestroy();
}
@Override
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 377d13860185..7bab7f159b7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -147,6 +147,7 @@ import com.android.systemui.SystemUIFactory;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingLog;
@@ -223,7 +224,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceP
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -394,6 +394,9 @@ public class StatusBar extends SystemUI implements DemoMode,
@Inject
protected NotifPipelineInitializer mNotifPipelineInitializer;
+ @VisibleForTesting
+ BroadcastDispatcher mBroadcastDispatcher;
+
// expanded notifications
protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
@@ -668,6 +671,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mBubbleController = Dependency.get(BubbleController.class);
mBubbleController.setExpandListener(mBubbleExpandListener);
mActivityIntentHelper = new ActivityIntentHelper(mContext);
+ mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
KeyguardSliceProvider sliceProvider = KeyguardSliceProvider.getAttachedInstance();
if (sliceProvider != null) {
sliceProvider.initDependencies(mMediaManager, mStatusBarStateController,
@@ -1049,11 +1053,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
// receive broadcasts
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
- context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
+ registerBroadcastReceiver();
IntentFilter demoFilter = new IntentFilter();
if (DEBUG_MEDIA_FAKE_ARTWORK) {
@@ -1074,6 +1074,15 @@ public class StatusBar extends SystemUI implements DemoMode,
ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f));
}
+ @VisibleForTesting
+ protected void registerBroadcastReceiver() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
+ mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, null, UserHandle.ALL);
+ }
+
protected QS createDefaultQSFragment() {
return FragmentHostManager.get(mStatusBarWindow).create(QSFragment.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 5bda34d64b73..ce929b7c621b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -28,6 +28,7 @@ import android.view.WindowManager.LayoutParams;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -138,20 +139,21 @@ public class SystemUIDialog extends AlertDialog {
private final Dialog mDialog;
private boolean mRegistered;
+ private final BroadcastDispatcher mBroadcastDispatcher;
DismissReceiver(Dialog dialog) {
mDialog = dialog;
+ mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
}
void register() {
- mDialog.getContext()
- .registerReceiverAsUser(this, UserHandle.CURRENT, INTENT_FILTER, null, null);
+ mBroadcastDispatcher.registerReceiver(this, INTENT_FILTER, null, UserHandle.CURRENT);
mRegistered = true;
}
void unregister() {
if (mRegistered) {
- mDialog.getContext().unregisterReceiver(this);
+ mBroadcastDispatcher.unregisterReceiver(this);
mRegistered = false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index c2c3f81527e8..b331fc3bf0ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
+
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -44,6 +46,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.settings.CurrentUserTracker;
@@ -60,6 +63,9 @@ import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
+import javax.inject.Inject;
+import javax.inject.Named;
+
/**
* Digital clock for the status bar.
*/
@@ -107,15 +113,20 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C
*/
private int mNonAdaptedColor;
- public Clock(Context context) {
- this(context, null);
- }
+ private final BroadcastDispatcher mBroadcastDispatcher;
public Clock(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
+ this(context, attrs, null);
+ }
+
+ @Inject
+ public Clock(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
+ BroadcastDispatcher broadcastDispatcher) {
+ this(context, attrs, 0, broadcastDispatcher);
}
- public Clock(Context context, AttributeSet attrs, int defStyle) {
+ public Clock(Context context, AttributeSet attrs, int defStyle,
+ BroadcastDispatcher broadcastDispatcher) {
super(context, attrs, defStyle);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
@@ -134,6 +145,7 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C
mCurrentUserId = newUserId;
}
};
+ mBroadcastDispatcher = broadcastDispatcher;
}
@Override
@@ -358,11 +370,11 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C
}
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
- mContext.registerReceiver(mScreenReceiver, filter);
+ mBroadcastDispatcher.registerReceiver(mScreenReceiver, filter);
}
} else {
if (mSecondsHandler != null) {
- mContext.unregisterReceiver(mScreenReceiver);
+ mBroadcastDispatcher.unregisterReceiver(mScreenReceiver);
mSecondsHandler.removeCallbacks(mSecondTick);
mSecondsHandler = null;
updateClock();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index f8c7532ec281..cc91bc082871 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -101,7 +101,9 @@ public class KeyguardStateControllerImpl extends KeyguardUpdateMonitorCallback
@Override
public void addCallback(@NonNull Callback callback) {
Preconditions.checkNotNull(callback, "Callback must not be null. b/128895449");
- mCallbacks.add(callback);
+ if (!mCallbacks.contains(callback)) {
+ mCallbacks.add(callback);
+ }
if (mCallbacks.size() != 0 && !mListening) {
mListening = true;
mKeyguardUpdateMonitor.registerCallback(this);
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index e44e58a84dc8..7e801da9cd1b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -37,6 +37,7 @@ import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.phone.LockIcon;
import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.policy.Clock;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -178,6 +179,11 @@ public class InjectionInflationController {
* Creates the QSCustomizer.
*/
QSCustomizer createQSCustomizer();
+
+ /**
+ * Creates a Clock.
+ */
+ Clock createClock();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index a6b5b38fd728..edea92f5952a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -59,6 +59,7 @@ import com.android.settingslib.volume.MediaSessions;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.qs.tiles.DndTile;
@@ -137,9 +138,10 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private UserActivityListener mUserActivityListener;
protected final VC mVolumeController = new VC();
+ protected final BroadcastDispatcher mBroadcastDispatcher;
@Inject
- public VolumeDialogControllerImpl(Context context) {
+ public VolumeDialogControllerImpl(Context context, BroadcastDispatcher broadcastDispatcher) {
mContext = context.getApplicationContext();
mNotificationManager = (NotificationManager) mContext.getSystemService(
Context.NOTIFICATION_SERVICE);
@@ -152,6 +154,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
mAudio = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mNoMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mObserver = new SettingObserver(mWorker);
+ mBroadcastDispatcher = broadcastDispatcher;
mObserver.init();
mReceiver.init();
mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
@@ -1004,11 +1007,11 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- mContext.registerReceiver(this, filter, null, mWorker);
+ mBroadcastDispatcher.registerReceiver(this, filter, mWorker);
}
public void destroy() {
- mContext.unregisterReceiver(this);
+ mBroadcastDispatcher.unregisterReceiver(this);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 011c2cd57588..e838d9e94a31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -70,6 +70,8 @@ class UserBroadcastDispatcherTest : SysuiTestCase() {
private lateinit var mockContext: Context
@Mock
private lateinit var mockHandler: Handler
+ @Mock
+ private lateinit var mPendingResult: BroadcastReceiver.PendingResult
@Captor
private lateinit var argumentCaptor: ArgumentCaptor<IntentFilter>
@@ -88,6 +90,7 @@ class UserBroadcastDispatcherTest : SysuiTestCase() {
universalBroadcastReceiver = UserBroadcastDispatcher(
mockContext, USER_ID, handler, testableLooper.looper)
+ universalBroadcastReceiver.pendingResult = mPendingResult
}
@Test
@@ -227,4 +230,19 @@ class UserBroadcastDispatcherTest : SysuiTestCase() {
verify(broadcastReceiver).onReceive(mockContext, intent)
verify(broadcastReceiverOther).onReceive(mockContext, intent)
}
+
+ @Test
+ fun testPendingResult() {
+ intentFilter = IntentFilter(ACTION_1)
+ universalBroadcastReceiver.registerReceiver(
+ ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+
+ val intent = Intent(ACTION_1)
+ universalBroadcastReceiver.onReceive(mockContext, intent)
+
+ testableLooper.processAllMessages()
+
+ verify(broadcastReceiver).onReceive(mockContext, intent)
+ verify(broadcastReceiver).pendingResult = mPendingResult
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 4d95f3f474b5..4958c649d532 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -19,6 +19,7 @@ import static android.provider.Settings.Global.SHOW_USB_TEMPERATURE_ALARM;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.anyObject;
import static org.mockito.Mockito.mock;
@@ -27,8 +28,11 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.IntentFilter;
import android.os.BatteryManager;
+import android.os.Handler;
import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.PowerManager;
@@ -43,6 +47,7 @@ import android.testing.TestableResources;
import com.android.settingslib.fuelgauge.Estimate;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.power.PowerUI.WarningsUI;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -80,6 +85,7 @@ public class PowerUITest extends SysuiTestCase {
@Mock private IThermalService mThermalServiceMock;
private IThermalEventListener mUsbThermalEventListener;
private IThermalEventListener mSkinThermalEventListener;
+ @Mock private BroadcastDispatcher mBroadcastDispatcher;
@Before
public void setup() {
@@ -96,6 +102,15 @@ public class PowerUITest extends SysuiTestCase {
}
@Test
+ public void testReceiverIsRegisteredToDispatcherOnStart() {
+ mPowerUI.start();
+ verify(mBroadcastDispatcher).registerReceiver(
+ any(BroadcastReceiver.class),
+ any(IntentFilter.class),
+ any(Handler.class)); //PowerUI does not call with User
+ }
+
+ @Test
public void testSkinWarning_throttlingCritical() throws Exception {
mPowerUI.start();
@@ -667,7 +682,7 @@ public class PowerUITest extends SysuiTestCase {
}
private void createPowerUi() {
- mPowerUI = new PowerUI();
+ mPowerUI = new PowerUI(mBroadcastDispatcher);
mPowerUI.mContext = mContext;
mPowerUI.mComponents = mContext.getComponents();
mPowerUI.mThermalService = mThermalServiceMock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 33d3ac848f0f..0bff5aa9e991 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -24,9 +24,11 @@ import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.annotation.LayoutRes;
@@ -34,11 +36,14 @@ import android.annotation.Nullable;
import android.app.Fragment;
import android.app.FragmentController;
import android.app.FragmentHostCallback;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.IntentFilter;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.LeakCheck.Tracker;
import android.testing.TestableLooper;
@@ -58,6 +63,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.SysuiTestableContext;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
@@ -70,6 +76,8 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper()
@@ -85,6 +93,8 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest {
private OverviewProxyService mOverviewProxyService;
private CommandQueue mCommandQueue;
private SysUiState mMockSysUiState;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
private AccessibilityManagerWrapper mAccessibilityWrapper =
new AccessibilityManagerWrapper(mContext) {
@@ -112,6 +122,8 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest {
@Before
public void setupFragment() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
setupSysuiDependency();
createRootView();
mOverviewProxyService =
@@ -177,6 +189,18 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
+ public void testRegisteredWithDispatcher() {
+ mFragments.dispatchResume();
+ processAllMessages();
+
+ verify(mBroadcastDispatcher).registerReceiver(
+ any(BroadcastReceiver.class),
+ any(IntentFilter.class),
+ any(Handler.class),
+ any(UserHandle.class));
+ }
+
+ @Test
public void testSetImeWindowStatusWhenImeSwitchOnDisplay() {
// Create default & external NavBar fragment.
NavigationBarFragment defaultNavBar = (NavigationBarFragment) mFragment;
@@ -227,7 +251,8 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest {
mOverviewProxyService,
mock(NavigationModeController.class),
mock(StatusBarStateController.class),
- mMockSysUiState);
+ mMockSysUiState,
+ mBroadcastDispatcher);
}
private class HostCallbacksForExternalDisplay extends
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 3be71c07009d..b75cb8cf487c 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
@@ -41,7 +41,9 @@ import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.StatusBarManager;
import android.app.trust.TrustManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.IntentFilter;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.fingerprint.FingerprintManager;
import android.metrics.LogMaker;
@@ -51,6 +53,7 @@ import android.os.IPowerManager;
import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.service.dreams.IDreamManager;
import android.support.test.metricshelper.MetricsAsserts;
import android.testing.AndroidTestingRunner;
@@ -75,6 +78,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
@@ -171,6 +175,8 @@ public class StatusBarTest extends SysuiTestCase {
private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
@Mock
private StatusBarWindowView mStatusBarWindowView;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
private TestableStatusBar mStatusBar;
private FakeMetricsLogger mMetricsLogger;
@@ -199,6 +205,7 @@ public class StatusBarTest extends SysuiTestCase {
mDependency.injectTestDependency(NotificationFilter.class, mNotificationFilter);
mDependency.injectTestDependency(NotificationAlertingManager.class,
mNotificationAlertingManager);
+ mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher);
IPowerManager powerManagerService = mock(IPowerManager.class);
mPowerManager = new PowerManager(mContext, powerManagerService,
@@ -263,7 +270,8 @@ public class StatusBarTest extends SysuiTestCase {
mDozeScrimController, mock(NotificationShelf.class),
mLockscreenUserManager, mCommandQueue, mNotificationPresenter,
mock(BubbleController.class), mock(NavigationBarController.class),
- mock(AutoHideController.class), mKeyguardUpdateMonitor, mStatusBarWindowView);
+ mock(AutoHideController.class), mKeyguardUpdateMonitor, mStatusBarWindowView,
+ mBroadcastDispatcher);
mStatusBar.mContext = mContext;
mStatusBar.mComponents = mContext.getComponents();
SystemUIFactory.getInstance().getRootComponent()
@@ -774,6 +782,16 @@ public class StatusBarTest extends SysuiTestCase {
verify(mNotificationPanelView, never()).expand(anyBoolean());
}
+ @Test
+ public void testRegisterBroadcastsonDispatcher() {
+ mStatusBar.registerBroadcastReceiver();
+ verify(mBroadcastDispatcher).registerReceiver(
+ any(BroadcastReceiver.class),
+ any(IntentFilter.class),
+ eq(null),
+ any(UserHandle.class));
+ }
+
static class TestableStatusBar extends StatusBar {
public TestableStatusBar(StatusBarKeyguardViewManager man,
KeyguardIndicationController key,
@@ -801,7 +819,8 @@ public class StatusBarTest extends SysuiTestCase {
NavigationBarController navBarController,
AutoHideController autoHideController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- StatusBarWindowView statusBarWindow) {
+ StatusBarWindowView statusBarWindow,
+ BroadcastDispatcher broadcastDispatcher) {
mStatusBarKeyguardViewManager = man;
mKeyguardIndicationController = key;
mStackScroller = stack;
@@ -835,6 +854,7 @@ public class StatusBarTest extends SysuiTestCase {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mStatusBarWindow = statusBarWindow;
mDozeServiceHost.mWakeLockScreenPerformsAuth = false;
+ mBroadcastDispatcher = broadcastDispatcher;
}
private WakefulnessLifecycle createAwakeWakefulnessLifecycle() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index a9a1392fb80b..589aa0353870 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -18,11 +18,9 @@ import static junit.framework.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.BroadcastReceiver;
-import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.testing.AndroidTestingRunner;
@@ -31,11 +29,14 @@ import android.testing.TestableLooper.RunWithLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -43,13 +44,16 @@ import org.mockito.ArgumentCaptor;
public class SystemUIDialogTest extends SysuiTestCase {
private SystemUIDialog mDialog;
-
- Context mContextSpy;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
@Before
public void setup() {
- mContextSpy = spy(mContext);
- mDialog = new SystemUIDialog(mContextSpy);
+ MockitoAnnotations.initMocks(this);
+
+ mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher);
+
+ mDialog = new SystemUIDialog(mContext);
}
@Test
@@ -60,12 +64,12 @@ public class SystemUIDialogTest extends SysuiTestCase {
ArgumentCaptor.forClass(IntentFilter.class);
mDialog.show();
- verify(mContextSpy).registerReceiverAsUser(broadcastReceiverCaptor.capture(), any(),
- intentFilterCaptor.capture(), any(), any());
+ verify(mBroadcastDispatcher).registerReceiver(broadcastReceiverCaptor.capture(),
+ intentFilterCaptor.capture(), eq(null), any());
assertTrue(intentFilterCaptor.getValue().hasAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
mDialog.dismiss();
- verify(mContextSpy).unregisterReceiver(eq(broadcastReceiverCaptor.getValue()));
+ verify(mBroadcastDispatcher).unregisterReceiver(eq(broadcastReceiverCaptor.getValue()));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index f4d0854b2c9f..2e945f2481d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -16,41 +16,64 @@
package com.android.systemui.volume;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.session.MediaSession;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.phone.StatusBar;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+@RunWith(AndroidTestingRunner.class)
@SmallTest
public class VolumeDialogControllerImplTest extends SysuiTestCase {
TestableVolumeDialogControllerImpl mVolumeController;
VolumeDialogControllerImpl.C mCallback;
StatusBar mStatusBar;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
@Before
public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
mCallback = mock(VolumeDialogControllerImpl.C.class);
mStatusBar = mock(StatusBar.class);
- mVolumeController = new TestableVolumeDialogControllerImpl(mContext, mCallback, mStatusBar);
+ mVolumeController = new TestableVolumeDialogControllerImpl(mContext, mCallback, mStatusBar,
+ mBroadcastDispatcher);
mVolumeController.setEnableDialogs(true, true);
}
@Test
+ public void testRegisteredWithDispatcher() {
+ verify(mBroadcastDispatcher).registerReceiver(
+ any(BroadcastReceiver.class),
+ any(IntentFilter.class),
+ any(Handler.class)); // VolumeDialogControllerImpl does not call with user
+ }
+
+ @Test
public void testVolumeChangeW_deviceNotInteractiveAOD() {
when(mStatusBar.isDeviceInteractive()).thenReturn(false);
when(mStatusBar.getWakefulnessState()).thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE);
@@ -81,7 +104,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
public void testVolumeChangeW_nullStatusBar() {
VolumeDialogControllerImpl.C callback = mock(VolumeDialogControllerImpl.C.class);
TestableVolumeDialogControllerImpl nullStatusBarTestableDialog = new
- TestableVolumeDialogControllerImpl(mContext, callback, null);
+ TestableVolumeDialogControllerImpl(mContext, callback, null, mBroadcastDispatcher);
nullStatusBarTestableDialog.setEnableDialogs(true, true);
nullStatusBarTestableDialog.onVolumeChangedW(0, AudioManager.FLAG_SHOW_UI);
verify(callback, times(1)).onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED);
@@ -100,8 +123,9 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
}
static class TestableVolumeDialogControllerImpl extends VolumeDialogControllerImpl {
- public TestableVolumeDialogControllerImpl(Context context, C callback, StatusBar s) {
- super(context);
+ TestableVolumeDialogControllerImpl(Context context, C callback, StatusBar s,
+ BroadcastDispatcher broadcastDispatcher) {
+ super(context, broadcastDispatcher);
mCallbacks = callback;
mStatusBar = s;
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 8e200196050c..b35300ceb399 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -78,6 +78,8 @@ final class SaveUi {
private static final int THEME_ID_DARK =
com.android.internal.R.style.Theme_DeviceDefault_Autofill_Save;
+ private static final int SCROLL_BAR_DEFAULT_DELAY_BEFORE_FADE_MS = 500;
+
public interface OnSaveListener {
void onSave();
void onCancel(IntentSender listener);
@@ -252,6 +254,8 @@ final class SaveUi {
new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
subtitleContainer.setVisibility(View.VISIBLE);
+ subtitleContainer.setScrollBarDefaultDelayBeforeFade(
+ SCROLL_BAR_DEFAULT_DELAY_BEFORE_FADE_MS);
}
if (sDebug) Slog.d(TAG, "on constructor: title=" + mTitle + ", subTitle=" + mSubTitle);
}
@@ -429,6 +433,9 @@ final class SaveUi {
saveUiView.findViewById(R.id.autofill_save_custom_subtitle);
subtitleContainer.addView(customSubtitleView);
subtitleContainer.setVisibility(View.VISIBLE);
+ subtitleContainer.setScrollBarDefaultDelayBeforeFade(
+ SCROLL_BAR_DEFAULT_DELAY_BEFORE_FADE_MS);
+
return true;
} catch (Exception e) {
Slog.e(TAG, "Error applying custom description. ", e);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index f70d511759f0..34111a92b66e 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -197,7 +197,7 @@ class StorageManagerService extends IStorageManager.Stub
"persist.sys.zram_enabled";
private static final boolean IS_FUSE_ENABLED =
- SystemProperties.getBoolean("persist.sys.fuse", false);
+ SystemProperties.getBoolean(StorageManager.PROP_FUSE, false);
private static final boolean ENABLE_ISOLATED_STORAGE = StorageManager.hasIsolatedStorage();
@@ -1526,6 +1526,9 @@ class StorageManagerService extends IStorageManager.Stub
SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE_SNAPSHOT, Boolean.toString(
SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, true)));
+ SystemProperties.set(StorageManager.PROP_FUSE_SNAPSHOT, Boolean.toString(
+ SystemProperties.getBoolean(StorageManager.PROP_FUSE, false)));
+
mContext = context;
mResolver = mContext.getContentResolver();
@@ -1858,7 +1861,7 @@ class StorageManagerService extends IStorageManager.Stub
// This means the mountUserId on such volumes is USER_NULL. This breaks fuse which
// requires a valid user to mount a volume. Create individual volumes per user in vold
// and remove this property check
- int userId = SystemProperties.getBoolean("persist.sys.fuse", false)
+ int userId = SystemProperties.getBoolean(StorageManager.PROP_FUSE_SNAPSHOT, false)
? mCurrentUserId : vol.mountUserId;
return mVold.mount(vol.id, vol.mountFlags, userId);
} catch (Exception e) {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index c412ebdc9033..5947c3599ca8 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1193,7 +1193,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
public void notifyCarrierNetworkChange(boolean active) {
// only CarrierService with carrier privilege rule should have the permission
int[] subIds = Arrays.stream(SubscriptionManager.from(mContext)
- .getActiveSubscriptionIdList())
+ .getActiveSubscriptionIdList(false))
.filter(i -> TelephonyPermissions.checkCarrierPrivilegeForSubId(i)).toArray();
if (ArrayUtils.isEmpty(subIds)) {
loge("notifyCarrierNetworkChange without carrier privilege");
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 33d8dec8b043..8e09d0e5958e 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -41,7 +41,8 @@ public class PlatformCompat extends IPlatformCompat.Stub {
public PlatformCompat(Context context) {
mContext = context;
- mChangeReporter = new ChangeReporter();
+ mChangeReporter = new ChangeReporter(
+ StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
}
@Override
@@ -96,10 +97,6 @@ public class PlatformCompat extends IPlatformCompat.Stub {
private void reportChange(long changeId, ApplicationInfo appInfo, int state) {
int uid = appInfo.uid;
- //TODO(b/138374585): Implement rate limiting for the logs.
- Slog.d(TAG, ChangeReporter.createLogString(uid, changeId, state));
- mChangeReporter.reportChange(uid, changeId,
- state, /* source */
- StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
+ mChangeReporter.reportChange(uid, changeId, state);
}
}
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index e8a577938b4c..88a7077ac37a 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -64,7 +64,13 @@ public class DisplayWhiteBalanceController implements
AmbientFilter mColorTemperatureFilter;
private DisplayWhiteBalanceThrottler mThrottler;
+ // In low brightness conditions the ALS readings are more noisy and produce
+ // high errors. This default is introduced to provide a fixed display color
+ // temperature when sensor readings become unreliable.
private final float mLowLightAmbientColorTemperature;
+ // In high brightness conditions certain color temperatures can cause peak display
+ // brightness to drop. This fixed color temperature can be used to compensate for
+ // this effect.
private final float mHighLightAmbientColorTemperature;
private float mAmbientColorTemperature;
@@ -85,12 +91,14 @@ public class DisplayWhiteBalanceController implements
// A piecewise linear relationship between ambient and display color temperatures.
private Spline.LinearSpline mAmbientToDisplayColorTemperatureSpline;
- // In very low or very high brightness conditions ambient EQ should to set to a default
- // instead of using mAmbientToDisplayColorTemperatureSpline. However, setting ambient EQ
- // based on thresholds can cause the display to rapidly change color temperature. To solve
- // this, mLowLightAmbientBrightnessToBiasSpline and mHighLightAmbientBrightnessToBiasSpline
- // are used to smoothly interpolate from ambient color temperature to the defaults.
- // A piecewise linear relationship between low light brightness and low light bias.
+ // In very low or very high brightness conditions Display White Balance should
+ // be to set to a default instead of using mAmbientToDisplayColorTemperatureSpline.
+ // However, setting Display White Balance based on thresholds can cause the
+ // display to rapidly change color temperature. To solve this,
+ // mLowLightAmbientBrightnessToBiasSpline and
+ // mHighLightAmbientBrightnessToBiasSpline are used to smoothly interpolate from
+ // ambient color temperature to the defaults. A piecewise linear relationship
+ // between low light brightness and low light bias.
private Spline.LinearSpline mLowLightAmbientBrightnessToBiasSpline;
// A piecewise linear relationship between high light brightness and high light bias.
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 248351ca3d2f..0aee8507d5af 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -456,6 +456,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
return;
}
mDestroyed = true;
+ mPlaybackState = null;
mHandler.post(MessageHandler.MSG_DESTROYED);
}
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index b831374cec45..c491da047927 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1697,10 +1697,11 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
return null;
}
+ DefaultBrowserProvider provider;
synchronized (mLock) {
- return mDefaultBrowserProvider == null
- ? null : mDefaultBrowserProvider.getDefaultBrowser(userId);
+ provider = mDefaultBrowserProvider;
}
+ return provider != null ? provider.getDefaultBrowser(userId) : null;
}
@Override
@@ -1716,23 +1717,27 @@ public class PermissionManagerService extends IPermissionManager.Stub {
private boolean setDefaultBrowserInternal(String packageName, boolean async,
boolean doGrant, int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ return false;
+ }
+ DefaultBrowserProvider provider;
synchronized (mLock) {
- if (userId == UserHandle.USER_ALL) {
- return false;
- }
- if (mDefaultBrowserProvider == null) {
+ provider = mDefaultBrowserProvider;
+ }
+ if (provider == null) {
+ return false;
+ }
+ if (async) {
+ provider.setDefaultBrowserAsync(packageName, userId);
+ } else {
+ if (!provider.setDefaultBrowser(packageName, userId)) {
return false;
}
- if (async) {
- mDefaultBrowserProvider.setDefaultBrowserAsync(packageName, userId);
- } else {
- if (!mDefaultBrowserProvider.setDefaultBrowser(packageName, userId)) {
- return false;
- }
- }
- if (doGrant && packageName != null) {
- mDefaultPermissionGrantPolicy
- .grantDefaultPermissionsToDefaultBrowser(packageName, userId);
+ }
+ if (doGrant && packageName != null) {
+ synchronized (mLock) {
+ mDefaultPermissionGrantPolicy.grantDefaultPermissionsToDefaultBrowser(packageName,
+ userId);
}
}
return true;
@@ -4334,15 +4339,17 @@ public class PermissionManagerService extends IPermissionManager.Stub {
@Override
public void setDefaultHome(String packageName, int userId, Consumer<Boolean> callback) {
+ if (userId == UserHandle.USER_ALL) {
+ return;
+ }
+ DefaultHomeProvider provider;
synchronized (mLock) {
- if (userId == UserHandle.USER_ALL) {
- return;
- }
- if (mDefaultHomeProvider == null) {
- return;
- }
- mDefaultHomeProvider.setDefaultHomeAsync(packageName, userId, callback);
+ provider = mDefaultHomeProvider;
+ }
+ if (provider == null) {
+ return;
}
+ provider.setDefaultHomeAsync(packageName, userId, callback);
}
@Override
@@ -4403,26 +4410,29 @@ public class PermissionManagerService extends IPermissionManager.Stub {
@Override
public String getDefaultBrowser(int userId) {
+ DefaultBrowserProvider provider;
synchronized (mLock) {
- return mDefaultBrowserProvider == null
- ? null : mDefaultBrowserProvider.getDefaultBrowser(userId);
+ provider = mDefaultBrowserProvider;
}
+ return provider != null ? provider.getDefaultBrowser(userId) : null;
}
@Override
public String getDefaultDialer(int userId) {
+ DefaultDialerProvider provider;
synchronized (mLock) {
- return mDefaultDialerProvider == null
- ? null : mDefaultDialerProvider.getDefaultDialer(userId);
+ provider = mDefaultDialerProvider;
}
+ return provider != null ? provider.getDefaultDialer(userId) : null;
}
@Override
public String getDefaultHome(int userId) {
+ DefaultHomeProvider provider;
synchronized (mLock) {
- return mDefaultHomeProvider == null
- ? null : mDefaultHomeProvider.getDefaultHome(userId);
+ provider = mDefaultHomeProvider;
}
+ return provider != null ? provider.getDefaultHome(userId) : null;
}
@Override
diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
index 3f9cc83b53d8..a9f44b9bbeb1 100644
--- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
+++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
@@ -78,7 +78,6 @@ public class AppDataRollbackHelper {
+ packageRollbackInfo.getPackageName() + ", userId: " + user, ie);
}
}
- packageRollbackInfo.getSnapshottedUsers().addAll(IntArray.wrap(userIds));
}
/**
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 2dc495197254..78b8b2e78772 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -16,14 +16,33 @@
package com.android.server.rollback;
+import static com.android.server.rollback.RollbackManagerServiceImpl.sendFailure;
+
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.UserManager;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.SparseLongArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import java.io.File;
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.ParseException;
@@ -41,10 +60,14 @@ import java.util.List;
* {@link PackageRollbackInfo} objects held within.
*/
class Rollback {
+
+ private static final String TAG = "RollbackManager";
+
@IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = {
ROLLBACK_STATE_ENABLING,
ROLLBACK_STATE_AVAILABLE,
ROLLBACK_STATE_COMMITTED,
+ ROLLBACK_STATE_DELETED
})
@Retention(RetentionPolicy.SOURCE)
@interface RollbackState {}
@@ -66,6 +89,11 @@ class Rollback {
static final int ROLLBACK_STATE_COMMITTED = 3;
/**
+ * The rollback has been deleted.
+ */
+ static final int ROLLBACK_STATE_DELETED = 4;
+
+ /**
* The session ID for the staged session if this rollback data represents a staged session,
* {@code -1} otherwise.
*/
@@ -234,19 +262,285 @@ class Rollback {
}
/**
- * Sets the state of the rollback to AVAILABLE.
+ * Returns true if the rollback is in the DELETED state.
+ */
+ @GuardedBy("getLock")
+ boolean isDeleted() {
+ return mState == ROLLBACK_STATE_DELETED;
+ }
+
+ /**
+ * Enables this rollback for the provided package.
+ *
+ * @return boolean True if the rollback was enabled successfully for the specified package.
*/
@GuardedBy("getLock")
- void setAvailable() {
+ boolean enableForPackage(String packageName, long newVersion, long installedVersion,
+ boolean isApex, String sourceDir, String[] splitSourceDirs) {
+ try {
+ RollbackStore.backupPackageCodePath(this, packageName, sourceDir);
+ if (!ArrayUtils.isEmpty(splitSourceDirs)) {
+ for (String dir : splitSourceDirs) {
+ RollbackStore.backupPackageCodePath(this, packageName, dir);
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to copy package for rollback for " + packageName, e);
+ return false;
+ }
+
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(
+ new VersionedPackage(packageName, newVersion),
+ new VersionedPackage(packageName, installedVersion),
+ new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
+ isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */);
+
+ info.getPackages().add(packageRollbackInfo);
+
+ return true;
+ }
+
+ /**
+ * Snapshots user data for the provided package and user ids. Does nothing if this rollback is
+ * not in the ENABLING state.
+ */
+ @GuardedBy("getLock")
+ void snapshotUserData(String packageName, int[] userIds, AppDataRollbackHelper dataHelper) {
+ if (!isEnabling()) {
+ return;
+ }
+
+ for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
+ if (pkgRollbackInfo.getPackageName().equals(packageName)) {
+ dataHelper.snapshotAppData(info.getRollbackId(), pkgRollbackInfo, userIds);
+
+ RollbackStore.saveRollback(this);
+ pkgRollbackInfo.getSnapshottedUsers().addAll(IntArray.wrap(userIds));
+ break;
+ }
+ }
+ }
+
+ /**
+ * Changes the state of the rollback to AVAILABLE. This also changes the timestamp to the
+ * current time and saves the rollback. Does nothing if this rollback is already in the
+ * DELETED state.
+ */
+ @GuardedBy("getLock")
+ void makeAvailable() {
+ if (isDeleted()) {
+ Slog.w(TAG, "Cannot make deleted rollback available.");
+ return;
+ }
mState = ROLLBACK_STATE_AVAILABLE;
+ mTimestamp = Instant.now();
+ RollbackStore.saveRollback(this);
+ }
+
+ /**
+ * Commits the rollback.
+ */
+ @GuardedBy("getLock")
+ void commit(final Context context, List<VersionedPackage> causePackages,
+ String callerPackageName, IntentSender statusReceiver) {
+
+ if (!isAvailable()) {
+ sendFailure(context, statusReceiver,
+ RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
+ "Rollback unavailable");
+ return;
+ }
+
+ // Get a context to use to install the downgraded version of the package.
+ Context pkgContext;
+ try {
+ pkgContext = context.createPackageContext(callerPackageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
+ "Invalid callerPackageName");
+ return;
+ }
+
+ PackageManager pm = pkgContext.getPackageManager();
+ try {
+ PackageInstaller packageInstaller = pm.getPackageInstaller();
+ PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ parentParams.setRequestDowngrade(true);
+ parentParams.setMultiPackage();
+ if (isStaged()) {
+ parentParams.setStaged();
+ }
+
+ int parentSessionId = packageInstaller.createSession(parentParams);
+ PackageInstaller.Session parentSession = packageInstaller.openSession(
+ parentSessionId);
+
+ for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ // TODO: We can't get the installerPackageName for apex
+ // (b/123920130). Is it okay to ignore the installer package
+ // for apex?
+ if (!pkgRollbackInfo.isApex()) {
+ String installerPackageName =
+ pm.getInstallerPackageName(pkgRollbackInfo.getPackageName());
+ if (installerPackageName != null) {
+ params.setInstallerPackageName(installerPackageName);
+ }
+ }
+ params.setRequestDowngrade(true);
+ params.setRequiredInstalledVersionCode(
+ pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode());
+ if (isStaged()) {
+ params.setStaged();
+ }
+ if (pkgRollbackInfo.isApex()) {
+ params.setInstallAsApex();
+ }
+ int sessionId = packageInstaller.createSession(params);
+ PackageInstaller.Session session = packageInstaller.openSession(sessionId);
+ File[] packageCodePaths = RollbackStore.getPackageCodePaths(
+ this, pkgRollbackInfo.getPackageName());
+ if (packageCodePaths == null) {
+ sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
+ "Backup copy of package inaccessible");
+ return;
+ }
+
+ for (File packageCodePath : packageCodePaths) {
+ try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCodePath,
+ ParcelFileDescriptor.MODE_READ_ONLY)) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ session.write(packageCodePath.getName(), 0,
+ packageCodePath.length(),
+ fd);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ parentSession.addChildSessionId(sessionId);
+ }
+
+ final LocalIntentReceiver receiver = new LocalIntentReceiver(
+ (Intent result) -> {
+ int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ if (status != PackageInstaller.STATUS_SUCCESS) {
+ // Committing the rollback failed, but we still have all the info we
+ // need to try rolling back again, so restore the rollback state to how
+ // it was before we tried committing.
+ // TODO: Should we just kill this rollback if commit failed?
+ // Why would we expect commit not to fail again?
+ // TODO: Could this cause a rollback to be resurrected
+ // if it should otherwise have expired by now?
+ synchronized (mLock) {
+ mState = ROLLBACK_STATE_AVAILABLE;
+ mRestoreUserDataInProgress = false;
+ }
+ sendFailure(context, statusReceiver,
+ RollbackManager.STATUS_FAILURE_INSTALL,
+ "Rollback downgrade install failed: "
+ + result.getStringExtra(
+ PackageInstaller.EXTRA_STATUS_MESSAGE));
+ return;
+ }
+
+ synchronized (mLock) {
+ if (!isStaged()) {
+ // All calls to restoreUserData should have
+ // completed by now for a non-staged install.
+ mRestoreUserDataInProgress = false;
+ }
+
+ info.setCommittedSessionId(parentSessionId);
+ info.getCausePackages().addAll(causePackages);
+ RollbackStore.deletePackageCodePaths(this);
+ RollbackStore.saveRollback(this);
+ }
+
+ // Send success.
+ try {
+ final Intent fillIn = new Intent();
+ fillIn.putExtra(
+ RollbackManager.EXTRA_STATUS, RollbackManager.STATUS_SUCCESS);
+ statusReceiver.sendIntent(context, 0, fillIn, null, null);
+ } catch (IntentSender.SendIntentException e) {
+ // Nowhere to send the result back to, so don't bother.
+ }
+
+ Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
+
+ for (UserInfo userInfo : UserManager.get(context).getUsers(true)) {
+ context.sendBroadcastAsUser(broadcast,
+ userInfo.getUserHandle(),
+ Manifest.permission.MANAGE_ROLLBACKS);
+ }
+ }
+ );
+
+ mState = ROLLBACK_STATE_COMMITTED;
+ mRestoreUserDataInProgress = true;
+ parentSession.commit(receiver.getIntentSender());
+ } catch (IOException e) {
+ Slog.e(TAG, "Rollback failed", e);
+ sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
+ "IOException: " + e.toString());
+ }
}
/**
- * Sets the state of the rollback to COMMITTED.
+ * Restores user data for the specified package if this rollback is currently marked as
+ * having a restore in progress.
+ *
+ * @return boolean True if this rollback has a restore in progress and contains the specified
+ * package.
*/
@GuardedBy("getLock")
- void setCommitted() {
- mState = ROLLBACK_STATE_COMMITTED;
+ boolean restoreUserDataForPackageIfInProgress(String packageName, int[] userIds, int appId,
+ String seInfo, AppDataRollbackHelper dataHelper) {
+ if (!isRestoreUserDataInProgress()) {
+ return false;
+ }
+
+ boolean foundPackage = false;
+ for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
+ if (pkgRollbackInfo.getPackageName().equals(packageName)) {
+ foundPackage = true;
+ boolean changedRollback = false;
+ for (int userId : userIds) {
+ changedRollback |= dataHelper.restoreAppData(
+ info.getRollbackId(), pkgRollbackInfo, userId, appId, seInfo);
+ }
+ // We've updated metadata about this rollback, so save it to flash.
+ if (changedRollback) {
+ RollbackStore.saveRollback(this);
+ }
+ break;
+ }
+ }
+ return foundPackage;
+ }
+
+ /**
+ * Deletes app data snapshots associated with this rollback, and moves to the DELETED state.
+ */
+ @GuardedBy("getLock")
+ void delete(AppDataRollbackHelper dataHelper) {
+ for (PackageRollbackInfo pkgInfo : info.getPackages()) {
+ IntArray snapshottedUsers = pkgInfo.getSnapshottedUsers();
+ for (int i = 0; i < snapshottedUsers.size(); i++) {
+ // Destroy app data snapshot.
+ int userId = snapshottedUsers.get(i);
+
+ dataHelper.destroyAppDataSnapshot(info.getRollbackId(), pkgInfo, userId);
+ }
+ }
+
+ RollbackStore.deleteRollback(this);
+ mState = ROLLBACK_STATE_DELETED;
}
/**
@@ -288,8 +582,8 @@ class Rollback {
*/
@GuardedBy("getLock")
boolean includesPackage(String packageName) {
- for (PackageRollbackInfo info : info.getPackages()) {
- if (info.getPackageName().equals(packageName)) {
+ for (PackageRollbackInfo packageRollbackInfo : info.getPackages()) {
+ if (packageRollbackInfo.getPackageName().equals(packageName)) {
return true;
}
}
@@ -302,9 +596,10 @@ class Rollback {
*/
@GuardedBy("getLock")
boolean includesPackageWithDifferentVersion(String packageName, long versionCode) {
- for (PackageRollbackInfo info : info.getPackages()) {
- if (info.getPackageName().equals(packageName)
- && info.getVersionRolledBackFrom().getLongVersionCode() != versionCode) {
+ for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
+ if (pkgRollbackInfo.getPackageName().equals(packageName)
+ && pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode()
+ != versionCode) {
return true;
}
}
@@ -317,8 +612,8 @@ class Rollback {
@GuardedBy("getLock")
List<String> getPackageNames() {
List<String> result = new ArrayList<>();
- for (PackageRollbackInfo info : info.getPackages()) {
- result.add(info.getPackageName());
+ for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
+ result.add(pkgRollbackInfo.getPackageName());
}
return result;
}
@@ -329,9 +624,9 @@ class Rollback {
@GuardedBy("getLock")
List<String> getApexPackageNames() {
List<String> result = new ArrayList<>();
- for (PackageRollbackInfo info : info.getPackages()) {
- if (info.isApex()) {
- result.add(info.getPackageName());
+ for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
+ if (pkgRollbackInfo.isApex()) {
+ result.add(pkgRollbackInfo.getPackageName());
}
}
return result;
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index e8e448aa118e..2221dff79336 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -43,7 +43,6 @@ import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -53,10 +52,8 @@ import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseBooleanArray;
-import android.util.SparseLongArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
@@ -64,7 +61,6 @@ import com.android.server.pm.Installer;
import java.io.File;
import java.io.FileDescriptor;
-import java.io.IOException;
import java.io.PrintWriter;
import java.security.SecureRandom;
import java.time.Instant;
@@ -339,7 +335,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
synchronized (rollback.getLock()) {
rollback.setTimestamp(
rollback.getTimestamp().plusMillis(timeDifference));
- saveRollback(rollback);
+ RollbackStore.saveRollback(rollback);
}
}
}
@@ -366,154 +362,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
Rollback rollback = getRollbackForId(rollbackId);
if (rollback == null) {
- sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
+ sendFailure(
+ mContext, statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
"Rollback unavailable");
return;
}
synchronized (rollback.getLock()) {
- if (!rollback.isAvailable()) {
- sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
- "Rollback unavailable");
- return;
- }
-
- // Get a context for the caller to use to install the downgraded
- // version of the package.
- final Context context;
- try {
- context = mContext.createPackageContext(callerPackageName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
- "Invalid callerPackageName");
- return;
- }
-
- PackageManager pm = context.getPackageManager();
- try {
- PackageInstaller packageInstaller = pm.getPackageInstaller();
- PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
- PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- parentParams.setRequestDowngrade(true);
- parentParams.setMultiPackage();
- if (rollback.isStaged()) {
- parentParams.setStaged();
- }
-
- int parentSessionId = packageInstaller.createSession(parentParams);
- PackageInstaller.Session parentSession = packageInstaller.openSession(
- parentSessionId);
-
- for (PackageRollbackInfo info : rollback.info.getPackages()) {
- PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
- PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- // TODO: We can't get the installerPackageName for apex
- // (b/123920130). Is it okay to ignore the installer package
- // for apex?
- if (!info.isApex()) {
- String installerPackageName =
- pm.getInstallerPackageName(info.getPackageName());
- if (installerPackageName != null) {
- params.setInstallerPackageName(installerPackageName);
- }
- }
- params.setRequestDowngrade(true);
- params.setRequiredInstalledVersionCode(
- info.getVersionRolledBackFrom().getLongVersionCode());
- if (rollback.isStaged()) {
- params.setStaged();
- }
- if (info.isApex()) {
- params.setInstallAsApex();
- }
- int sessionId = packageInstaller.createSession(params);
- PackageInstaller.Session session = packageInstaller.openSession(sessionId);
- File[] packageCodePaths = RollbackStore.getPackageCodePaths(
- rollback, info.getPackageName());
- if (packageCodePaths == null) {
- sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
- "Backup copy of package inaccessible");
- return;
- }
-
- for (File packageCodePath : packageCodePaths) {
- try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCodePath,
- ParcelFileDescriptor.MODE_READ_ONLY)) {
- final long token = Binder.clearCallingIdentity();
- try {
- session.write(packageCodePath.getName(), 0,
- packageCodePath.length(),
- fd);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- }
- parentSession.addChildSessionId(sessionId);
- }
-
- final LocalIntentReceiver receiver = new LocalIntentReceiver(
- (Intent result) -> getHandler().post(() -> {
-
- int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_FAILURE);
- if (status != PackageInstaller.STATUS_SUCCESS) {
- // Committing the rollback failed, but we
- // still have all the info we need to try
- // rolling back again, so restore the rollback
- // state to how it was before we tried
- // committing.
- // TODO: Should we just kill this rollback if
- // commit failed? Why would we expect commit
- // not to fail again?
- // TODO: Could this cause a rollback to be
- // resurrected if it should otherwise have
- // expired by now?
- synchronized (rollback.getLock()) {
- rollback.setAvailable();
- rollback.setRestoreUserDataInProgress(false);
- }
- sendFailure(statusReceiver,
- RollbackManager.STATUS_FAILURE_INSTALL,
- "Rollback downgrade install failed: "
- + result.getStringExtra(
- PackageInstaller.EXTRA_STATUS_MESSAGE));
- return;
- }
-
- synchronized (rollback.getLock()) {
- if (!rollback.isStaged()) {
- // All calls to restoreUserData should have
- // completed by now for a non-staged install.
- rollback.setRestoreUserDataInProgress(false);
- }
-
- rollback.info.setCommittedSessionId(parentSessionId);
- rollback.info.getCausePackages().addAll(causePackages);
- RollbackStore.deletePackageCodePaths(rollback);
- saveRollback(rollback);
- }
-
- sendSuccess(statusReceiver);
-
- Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
-
- for (UserInfo userInfo : UserManager.get(mContext).getUsers(true)) {
- mContext.sendBroadcastAsUser(broadcast,
- userInfo.getUserHandle(),
- Manifest.permission.MANAGE_ROLLBACKS);
- }
- })
- );
-
- rollback.setCommitted();
- rollback.setRestoreUserDataInProgress(true);
- parentSession.commit(receiver.getIntentSender());
- } catch (IOException e) {
- Slog.e(TAG, "Rollback failed", e);
- sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
- "IOException: " + e.toString());
- return;
- }
+ rollback.commit(mContext, causePackages, callerPackageName, statusReceiver);
}
}
@@ -552,7 +407,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
synchronized (rollback.getLock()) {
if (rollback.includesPackage(packageName)) {
iter.remove();
- deleteRollback(rollback);
+ rollback.delete(mAppDataRollbackHelper);
}
}
}
@@ -596,7 +451,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
synchronized (rollback.getLock()) {
if (mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(
userId, rollback)) {
- saveRollback(rollback);
+ RollbackStore.saveRollback(rollback);
}
}
}
@@ -658,7 +513,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
// mRollbacks, or is it okay to leave as
// unavailable until the next reboot when it will go
// away on its own?
- deleteRollback(rollback);
+ rollback.delete(mAppDataRollbackHelper);
} else if (session.isStagedSessionApplied()) {
makeRollbackAvailable(rollback);
}
@@ -674,7 +529,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
if (session != null) {
if (session.isStagedSessionApplied() || session.isStagedSessionFailed()) {
rollback.setRestoreUserDataInProgress(false);
- saveRollback(rollback);
+ RollbackStore.saveRollback(rollback);
}
}
}
@@ -716,7 +571,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
&& rollback.includesPackageWithDifferentVersion(packageName,
installedVersion)) {
iter.remove();
- deleteRollback(rollback);
+ rollback.delete(mAppDataRollbackHelper);
}
}
}
@@ -738,27 +593,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
* @param status the RollbackManager.STATUS_* code with the failure.
* @param message the failure message.
*/
- private void sendFailure(IntentSender statusReceiver, @RollbackManager.Status int status,
- String message) {
+ static void sendFailure(Context context, IntentSender statusReceiver,
+ @RollbackManager.Status int status, String message) {
Slog.e(TAG, message);
try {
final Intent fillIn = new Intent();
fillIn.putExtra(RollbackManager.EXTRA_STATUS, status);
fillIn.putExtra(RollbackManager.EXTRA_STATUS_MESSAGE, message);
- statusReceiver.sendIntent(mContext, 0, fillIn, null, null);
- } catch (IntentSender.SendIntentException e) {
- // Nowhere to send the result back to, so don't bother.
- }
- }
-
- /**
- * Notifies an IntentSender of success.
- */
- private void sendSuccess(IntentSender statusReceiver) {
- try {
- final Intent fillIn = new Intent();
- fillIn.putExtra(RollbackManager.EXTRA_STATUS, RollbackManager.STATUS_SUCCESS);
- statusReceiver.sendIntent(mContext, 0, fillIn, null, null);
+ statusReceiver.sendIntent(context, 0, fillIn, null, null);
} catch (IntentSender.SendIntentException e) {
// Nowhere to send the result back to, so don't bother.
}
@@ -781,7 +623,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
rollback.getTimestamp()
.plusMillis(mRollbackLifetimeDurationInMillis))) {
iter.remove();
- deleteRollback(rollback);
+ rollback.delete(mAppDataRollbackHelper);
} else if (oldest == null || oldest.isAfter(rollback.getTimestamp())) {
oldest = rollback.getTimestamp();
}
@@ -954,7 +796,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
// Get information about the package to be installed.
- PackageParser.PackageLite newPackage = null;
+ PackageParser.PackageLite newPackage;
try {
newPackage = PackageParser.parsePackageLite(new File(session.resolvedBaseCodePath), 0);
} catch (PackageParser.PackageParserException e) {
@@ -973,11 +815,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
return false;
}
- VersionedPackage newVersion = new VersionedPackage(packageName, newPackage.versionCode);
final boolean isApex = ((installFlags & PackageManager.INSTALL_APEX) != 0);
// Get information about the currently installed package.
- PackageManager pm = mContext.getPackageManager();
final PackageInfo pkgInfo;
try {
pkgInfo = getPackageInfo(packageName);
@@ -988,32 +828,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
return false;
}
- VersionedPackage installedVersion = new VersionedPackage(packageName,
- pkgInfo.getLongVersionCode());
-
- PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(
- newVersion, installedVersion,
- new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
- isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */);
-
-
- try {
- ApplicationInfo appInfo = pkgInfo.applicationInfo;
- RollbackStore.backupPackageCodePath(rollback, packageName, appInfo.sourceDir);
- if (!ArrayUtils.isEmpty(appInfo.splitSourceDirs)) {
- for (String sourceDir : appInfo.splitSourceDirs) {
- RollbackStore.backupPackageCodePath(rollback, packageName, sourceDir);
- }
- }
- } catch (IOException e) {
- Slog.e(TAG, "Unable to copy package for rollback for " + packageName, e);
- return false;
- }
-
+ ApplicationInfo appInfo = pkgInfo.applicationInfo;
synchronized (rollback.getLock()) {
- rollback.info.getPackages().add(packageRollbackInfo);
+ return rollback.enableForPackage(packageName, newPackage.versionCode,
+ pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
+ appInfo.splitSourceDirs);
}
- return true;
}
@Override
@@ -1026,7 +846,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
getHandler().post(() -> {
snapshotUserDataInternal(packageName, userIds);
- restoreUserDataInternal(packageName, userIds, appId, ceDataInode, seInfo, token);
+ restoreUserDataInternal(packageName, userIds, appId, seInfo);
final PackageManagerInternal pmi = LocalServices.getService(
PackageManagerInternal.class);
pmi.finishPackageInstall(token, false);
@@ -1039,69 +859,32 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
for (int i = 0; i < mRollbacks.size(); i++) {
Rollback rollback = mRollbacks.get(i);
synchronized (rollback.getLock()) {
- if (!rollback.isEnabling()) {
- continue;
- }
-
- for (PackageRollbackInfo info : rollback.info.getPackages()) {
- if (info.getPackageName().equals(packageName)) {
- mAppDataRollbackHelper.snapshotAppData(
- rollback.info.getRollbackId(), info, userIds);
- saveRollback(rollback);
- break;
- }
- }
+ rollback.snapshotUserData(packageName, userIds, mAppDataRollbackHelper);
}
}
// non-staged installs
- PackageRollbackInfo info;
for (NewRollback rollback : mNewRollbacks) {
synchronized (rollback.rollback.getLock()) {
- info = getPackageRollbackInfo(rollback.rollback, packageName);
- if (info != null) {
- mAppDataRollbackHelper.snapshotAppData(
- rollback.rollback.info.getRollbackId(), info, userIds);
- saveRollback(rollback.rollback);
- }
+ rollback.rollback.snapshotUserData(
+ packageName, userIds, mAppDataRollbackHelper);
}
}
}
}
- private void restoreUserDataInternal(String packageName, int[] userIds, int appId,
- long ceDataInode, String seInfo, int token) {
- PackageRollbackInfo info = null;
- Rollback rollback = null;
+ private void restoreUserDataInternal(
+ String packageName, int[] userIds, int appId, String seInfo) {
synchronized (mLock) {
for (int i = 0; i < mRollbacks.size(); ++i) {
- Rollback candidate = mRollbacks.get(i);
- synchronized (candidate.getLock()) {
- if (candidate.isRestoreUserDataInProgress()) {
- info = getPackageRollbackInfo(candidate, packageName);
- if (info != null) {
- rollback = candidate;
- break;
- }
+ Rollback rollback = mRollbacks.get(i);
+ synchronized (rollback.getLock()) {
+ if (rollback.restoreUserDataForPackageIfInProgress(
+ packageName, userIds, appId, seInfo, mAppDataRollbackHelper)) {
+ return;
}
}
}
}
-
- if (rollback == null) {
- return;
- }
-
- for (int userId : userIds) {
- synchronized (rollback.getLock()) {
- final boolean changedRollback = mAppDataRollbackHelper.restoreAppData(
- rollback.info.getRollbackId(), info, userId, appId, seInfo);
-
- // We've updated metadata about this rollback, so save it to flash.
- if (changedRollback) {
- saveRollback(rollback);
- }
- }
- }
}
@Override
@@ -1189,7 +972,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
if (rollback != null) {
synchronized (rollback.getLock()) {
rollback.setApkSessionId(apkSessionId);
- saveRollback(rollback);
+ RollbackStore.saveRollback(rollback);
}
}
});
@@ -1238,8 +1021,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
* Returns -1 if the package is not currently installed.
*/
private long getInstalledPackageVersion(String packageName) {
- PackageManager pm = mContext.getPackageManager();
- PackageInfo pkgInfo = null;
+ PackageInfo pkgInfo;
try {
pkgInfo = getPackageInfo(packageName);
} catch (PackageManager.NameNotFoundException e) {
@@ -1250,9 +1032,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
/**
- * Gets PackageInfo for the given package.
- * Matches any user and apex. Returns null if no such package is
- * installed.
+ * Gets PackageInfo for the given package. Matches any user and apex.
+ *
+ * @throws PackageManager.NameNotFoundException if no such package is installed.
*/
private PackageInfo getPackageInfo(String packageName)
throws PackageManager.NameNotFoundException {
@@ -1268,13 +1050,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
}
-
- private boolean packageVersionsEqual(VersionedPackage a, VersionedPackage b) {
- return a != null && b != null
- && a.getPackageName().equals(b.getPackageName())
- && a.getLongVersionCode() == b.getLongVersionCode();
- }
-
private class SessionCallback extends PackageInstaller.SessionCallback {
@Override
@@ -1326,23 +1101,23 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
synchronized (rollback.getLock()) {
if (!success) {
// The install session was aborted, clean up the pending install.
- deleteRollback(rollback);
+ rollback.delete(mAppDataRollbackHelper);
return null;
}
if (newRollback.isCancelled) {
Slog.e(TAG, "Rollback has been cancelled by PackageManager");
- deleteRollback(rollback);
+ rollback.delete(mAppDataRollbackHelper);
return null;
}
if (rollback.info.getPackages().size() != newRollback.packageSessionIds.length) {
Slog.e(TAG, "Failed to enable rollback for all packages in session.");
- deleteRollback(rollback);
+ rollback.delete(mAppDataRollbackHelper);
return null;
}
- saveRollback(rollback);
+ RollbackStore.saveRollback(rollback);
}
synchronized (mLock) {
// Note: There is a small window of time between when
@@ -1363,17 +1138,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
@GuardedBy("rollback.getLock")
private void makeRollbackAvailable(Rollback rollback) {
- // TODO: What if the rollback has since been expired, for example due
- // to a new package being installed. Won't this revive an expired
- // rollback? Consider adding a ROLLBACK_STATE_EXPIRED to address this.
- rollback.setAvailable();
- rollback.setTimestamp(Instant.now());
- saveRollback(rollback);
+ rollback.makeAvailable();
// TODO(zezeozue): Provide API to explicitly start observing instead
// of doing this for all rollbacks. If we do this for all rollbacks,
// should document in PackageInstaller.SessionParams#setEnableRollback
- // After enabling and commiting any rollback, observe packages and
+ // After enabling and committing any rollback, observe packages and
// prepare to rollback if packages crashes too frequently.
mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
mRollbackLifetimeDurationInMillis);
@@ -1396,22 +1166,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
return null;
}
- /**
- * Returns the {@code PackageRollbackInfo} associated with {@code packageName} from
- * a specified {@code Rollback}.
- */
- @GuardedBy("rollback.getLock")
- private static PackageRollbackInfo getPackageRollbackInfo(Rollback rollback,
- String packageName) {
- for (PackageRollbackInfo info : rollback.info.getPackages()) {
- if (info.getPackageName().equals(packageName)) {
- return info;
- }
- }
-
- return null;
- }
-
@GuardedBy("mLock")
private int allocateRollbackIdLocked() {
int n = 0;
@@ -1427,35 +1181,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
throw new IllegalStateException("Failed to allocate rollback ID");
}
- @GuardedBy("rollback.getLock")
- private void deleteRollback(Rollback rollback) {
- for (PackageRollbackInfo info : rollback.info.getPackages()) {
- IntArray snapshottedUsers = info.getSnapshottedUsers();
- for (int i = 0; i < snapshottedUsers.size(); i++) {
- int userId = snapshottedUsers.get(i);
- mAppDataRollbackHelper.destroyAppDataSnapshot(rollback.info.getRollbackId(),
- info, userId);
- }
- }
- mRollbackStore.deleteRollback(rollback);
- }
-
- /**
- * Saves a rollback, swallowing any IOExceptions.
- * For those times when it's not obvious what to do about the IOException.
- * TODO: Double check we can't do a better job handling the IOException in
- * a cases where this method is called.
- */
- @GuardedBy("rollback.getLock")
- private void saveRollback(Rollback rollback) {
- try {
- mRollbackStore.saveRollback(rollback);
- } catch (IOException ioe) {
- Slog.e(TAG, "Unable to save rollback for: "
- + rollback.info.getRollbackId(), ioe);
- }
- }
-
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index b6d1f1875907..a9331aa5648f 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -253,7 +253,7 @@ class RollbackStore {
* Saves the given rollback to persistent storage.
*/
@GuardedBy("rollback.getLock")
- void saveRollback(Rollback rollback) throws IOException {
+ static void saveRollback(Rollback rollback) {
try {
JSONObject dataJson = new JSONObject();
dataJson.put("info", rollbackInfoToJson(rollback.info));
@@ -266,15 +266,15 @@ class RollbackStore {
PrintWriter pw = new PrintWriter(new File(rollback.getBackupDir(), "rollback.json"));
pw.println(dataJson.toString());
pw.close();
- } catch (JSONException e) {
- throw new IOException(e);
+ } catch (JSONException | IOException e) {
+ Slog.e(TAG, "Unable to save rollback for: " + rollback.info.getRollbackId(), e);
}
}
/**
* Removes all persistent storage associated with the given rollback.
*/
- void deleteRollback(Rollback rollback) {
+ static void deleteRollback(Rollback rollback) {
removeFile(rollback.getBackupDir());
}
diff --git a/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java b/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java
index f8ffb7c1c0e2..b7bc77dc97ee 100644
--- a/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java
+++ b/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java
@@ -21,8 +21,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.tv.ITvRemoteProvider;
-import android.media.tv.ITvRemoteServiceInput;
-import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -30,44 +28,33 @@ import android.util.Log;
import android.util.Slog;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
/**
* Maintains a connection to a tv remote provider service.
*/
final class TvRemoteProviderProxy implements ServiceConnection {
- private static final String TAG = "TvRemoteProvProxy"; // max. 23 chars
+ private static final String TAG = "TvRemoteProviderProxy";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
- private static final boolean DEBUG_KEY = false;
// This should match TvRemoteProvider.ACTION_TV_REMOTE_PROVIDER
protected static final String SERVICE_INTERFACE =
"com.android.media.tv.remoteprovider.TvRemoteProvider";
private final Context mContext;
+ private final Object mLock;
private final ComponentName mComponentName;
private final int mUserId;
private final int mUid;
- /**
- * State guarded by mLock.
- * This is the first lock in sequence for an incoming call.
- * The second lock is always {@link TvRemoteService#mLock}
- *
- * There are currently no methods that break this sequence.
- */
- private final Object mLock = new Object();
-
- private ProviderMethods mProviderMethods;
- // Connection state
+ // State changes happen only in the main thread, hence no lock is needed
private boolean mRunning;
private boolean mBound;
- private Connection mActiveConnection;
+ private boolean mConnected;
- TvRemoteProviderProxy(Context context, ProviderMethods provider,
+ TvRemoteProviderProxy(Context context, Object lock,
ComponentName componentName, int userId, int uid) {
mContext = context;
- mProviderMethods = provider;
+ mLock = lock;
mComponentName = componentName;
mUserId = userId;
mUid = uid;
@@ -78,7 +65,7 @@ final class TvRemoteProviderProxy implements ServiceConnection {
pw.println(prefix + " mUserId=" + mUserId);
pw.println(prefix + " mRunning=" + mRunning);
pw.println(prefix + " mBound=" + mBound);
- pw.println(prefix + " mActiveConnection=" + mActiveConnection);
+ pw.println(prefix + " mConnected=" + mConnected);
}
public boolean hasComponentName(String packageName, String className) {
@@ -109,11 +96,9 @@ final class TvRemoteProviderProxy implements ServiceConnection {
}
public void rebindIfDisconnected() {
- synchronized (mLock) {
- if (mActiveConnection == null && mRunning) {
- unbind();
- bind();
- }
+ if (mRunning && !mConnected) {
+ unbind();
+ bind();
}
}
@@ -129,7 +114,7 @@ final class TvRemoteProviderProxy implements ServiceConnection {
mBound = mContext.bindServiceAsUser(service, this,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
new UserHandle(mUserId));
- if (!mBound && DEBUG) {
+ if (DEBUG && !mBound) {
Slog.d(TAG, this + ": Bind failed");
}
} catch (SecurityException ex) {
@@ -147,7 +132,6 @@ final class TvRemoteProviderProxy implements ServiceConnection {
}
mBound = false;
- disconnect();
mContext.unbindService(this);
}
}
@@ -158,392 +142,27 @@ final class TvRemoteProviderProxy implements ServiceConnection {
Slog.d(TAG, this + ": onServiceConnected()");
}
- if (mBound) {
- disconnect();
+ mConnected = true;
- ITvRemoteProvider provider = ITvRemoteProvider.Stub.asInterface(service);
- if (provider != null) {
- Connection connection = new Connection(provider);
- if (connection.register()) {
- synchronized (mLock) {
- mActiveConnection = connection;
- }
- if (DEBUG) {
- Slog.d(TAG, this + ": Connected successfully.");
- }
- } else {
- if (DEBUG) {
- Slog.d(TAG, this + ": Registration failed");
- }
- }
- } else {
- Slog.e(TAG, this + ": Service returned invalid remote-control provider binder");
- }
+ final ITvRemoteProvider provider = ITvRemoteProvider.Stub.asInterface(service);
+ if (provider == null) {
+ Slog.e(TAG, this + ": Invalid binder");
+ return;
}
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) Slog.d(TAG, this + ": Service disconnected");
- disconnect();
- }
-
- private void disconnect() {
- synchronized (mLock) {
- if (mActiveConnection != null) {
- mActiveConnection.dispose();
- mActiveConnection = null;
- }
+ try {
+ provider.setRemoteServiceInputSink(new TvRemoteServiceInput(mLock, provider));
+ } catch (RemoteException e) {
+ Slog.e(TAG, this + ": Failed remote call to setRemoteServiceInputSink");
}
}
- interface ProviderMethods {
- // InputBridge
- boolean openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name,
- int width, int height, int maxPointers);
-
- void closeInputBridge(TvRemoteProviderProxy provider, IBinder token);
-
- void clearInputBridge(TvRemoteProviderProxy provider, IBinder token);
-
- void sendKeyDown(TvRemoteProviderProxy provider, IBinder token, int keyCode);
-
- void sendKeyUp(TvRemoteProviderProxy provider, IBinder token, int keyCode);
-
- void sendPointerDown(TvRemoteProviderProxy provider, IBinder token, int pointerId, int x,
- int y);
-
- void sendPointerUp(TvRemoteProviderProxy provider, IBinder token, int pointerId);
-
- void sendPointerSync(TvRemoteProviderProxy provider, IBinder token);
- }
-
- private final class Connection {
- private final ITvRemoteProvider mTvRemoteProvider;
- private final RemoteServiceInputProvider mServiceInputProvider;
-
- public Connection(ITvRemoteProvider provider) {
- mTvRemoteProvider = provider;
- mServiceInputProvider = new RemoteServiceInputProvider(this);
- }
-
- public boolean register() {
- if (DEBUG) Slog.d(TAG, "Connection::register()");
- try {
- mTvRemoteProvider.setRemoteServiceInputSink(mServiceInputProvider);
- return true;
- } catch (RemoteException ex) {
- dispose();
- return false;
- }
- }
-
- public void dispose() {
- if (DEBUG) Slog.d(TAG, "Connection::dispose()");
- mServiceInputProvider.dispose();
- }
-
-
- public void onInputBridgeConnected(IBinder token) {
- if (DEBUG) Slog.d(TAG, this + ": onInputBridgeConnected");
- try {
- mTvRemoteProvider.onInputBridgeConnected(token);
- } catch (RemoteException ex) {
- Slog.e(TAG, "Failed to deliver onInputBridgeConnected. ", ex);
- }
- }
-
- void openInputBridge(final IBinder token, final String name, final int width,
- final int height, final int maxPointers) {
- synchronized (mLock) {
- if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
- if (DEBUG) {
- Slog.d(TAG, this + ": openInputBridge," +
- " token=" + token + ", name=" + name);
- }
- final long idToken = Binder.clearCallingIdentity();
- try {
- if (mProviderMethods.openInputBridge(TvRemoteProviderProxy.this, token,
- name, width, height, maxPointers)) {
- onInputBridgeConnected(token);
- }
- } finally {
- Binder.restoreCallingIdentity(idToken);
- }
- } else {
- if (DEBUG) {
- Slog.w(TAG,
- "openInputBridge, Invalid connection or incorrect uid: " + Binder
- .getCallingUid());
- }
- }
- }
- }
-
- void closeInputBridge(final IBinder token) {
- synchronized (mLock) {
- if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
- if (DEBUG) {
- Slog.d(TAG, this + ": closeInputBridge," +
- " token=" + token);
- }
- final long idToken = Binder.clearCallingIdentity();
- try {
- mProviderMethods.closeInputBridge(TvRemoteProviderProxy.this, token);
- } finally {
- Binder.restoreCallingIdentity(idToken);
- }
- } else {
- if (DEBUG) {
- Slog.w(TAG,
- "closeInputBridge, Invalid connection or incorrect uid: " +
- Binder.getCallingUid());
- }
- }
- }
- }
-
- void clearInputBridge(final IBinder token) {
- synchronized (mLock) {
- if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
- if (DEBUG) {
- Slog.d(TAG, this + ": clearInputBridge," +
- " token=" + token);
- }
- final long idToken = Binder.clearCallingIdentity();
- try {
- mProviderMethods.clearInputBridge(TvRemoteProviderProxy.this, token);
- } finally {
- Binder.restoreCallingIdentity(idToken);
- }
- } else {
- if (DEBUG) {
- Slog.w(TAG,
- "clearInputBridge, Invalid connection or incorrect uid: " +
- Binder.getCallingUid());
- }
- }
- }
- }
-
- void sendTimestamp(final IBinder token, final long timestamp) {
- if (DEBUG) {
- Slog.e(TAG, "sendTimestamp is deprecated, please remove all usages of this API.");
- }
- }
-
- void sendKeyDown(final IBinder token, final int keyCode) {
- synchronized (mLock) {
- if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
- if (DEBUG_KEY) {
- Slog.d(TAG, this + ": sendKeyDown," +
- " token=" + token + ", keyCode=" + keyCode);
- }
- final long idToken = Binder.clearCallingIdentity();
- try {
- mProviderMethods.sendKeyDown(TvRemoteProviderProxy.this, token, keyCode);
- } finally {
- Binder.restoreCallingIdentity(idToken);
- }
- } else {
- if (DEBUG) {
- Slog.w(TAG,
- "sendKeyDown, Invalid connection or incorrect uid: " + Binder
- .getCallingUid());
- }
- }
- }
- }
-
- void sendKeyUp(final IBinder token, final int keyCode) {
- synchronized (mLock) {
- if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
- if (DEBUG_KEY) {
- Slog.d(TAG, this + ": sendKeyUp," +
- " token=" + token + ", keyCode=" + keyCode);
- }
- final long idToken = Binder.clearCallingIdentity();
- try {
- mProviderMethods.sendKeyUp(TvRemoteProviderProxy.this, token, keyCode);
- } finally {
- Binder.restoreCallingIdentity(idToken);
- }
- } else {
- if (DEBUG) {
- Slog.w(TAG,
- "sendKeyUp, Invalid connection or incorrect uid: " + Binder
- .getCallingUid());
- }
- }
- }
- }
-
- void sendPointerDown(final IBinder token, final int pointerId, final int x, final int y) {
- synchronized (mLock) {
- if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
- if (DEBUG_KEY) {
- Slog.d(TAG, this + ": sendPointerDown," +
- " token=" + token + ", pointerId=" + pointerId);
- }
- final long idToken = Binder.clearCallingIdentity();
- try {
- mProviderMethods.sendPointerDown(TvRemoteProviderProxy.this, token,
- pointerId, x, y);
- } finally {
- Binder.restoreCallingIdentity(idToken);
- }
- } else {
- if (DEBUG) {
- Slog.w(TAG,
- "sendPointerDown, Invalid connection or incorrect uid: " + Binder
- .getCallingUid());
- }
- }
- }
- }
-
- void sendPointerUp(final IBinder token, final int pointerId) {
- synchronized (mLock) {
- if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
- if (DEBUG_KEY) {
- Slog.d(TAG, this + ": sendPointerUp," +
- " token=" + token + ", pointerId=" + pointerId);
- }
- final long idToken = Binder.clearCallingIdentity();
- try {
- mProviderMethods.sendPointerUp(TvRemoteProviderProxy.this, token,
- pointerId);
- } finally {
- Binder.restoreCallingIdentity(idToken);
- }
- } else {
- if (DEBUG) {
- Slog.w(TAG,
- "sendPointerUp, Invalid connection or incorrect uid: " + Binder
- .getCallingUid());
- }
- }
- }
- }
-
- void sendPointerSync(final IBinder token) {
- synchronized (mLock) {
- if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
- if (DEBUG_KEY) {
- Slog.d(TAG, this + ": sendPointerSync," +
- " token=" + token);
- }
- final long idToken = Binder.clearCallingIdentity();
- try {
- if (mProviderMethods != null) {
- mProviderMethods.sendPointerSync(TvRemoteProviderProxy.this, token);
- }
- } finally {
- Binder.restoreCallingIdentity(idToken);
- }
- } else {
- if (DEBUG) {
- Slog.w(TAG,
- "sendPointerSync, Invalid connection or incorrect uid: " + Binder
- .getCallingUid());
- }
- }
- }
- }
- }
-
- /**
- * Receives events from the connected provider.
- * <p>
- * This inner class is static and only retains a weak reference to the connection
- * to prevent the client from being leaked in case the service is holding an
- * active reference to the client's callback.
- * </p>
- */
- private static final class RemoteServiceInputProvider extends ITvRemoteServiceInput.Stub {
- private final WeakReference<Connection> mConnectionRef;
-
- public RemoteServiceInputProvider(Connection connection) {
- mConnectionRef = new WeakReference<Connection>(connection);
- }
-
- public void dispose() {
- // Terminate the connection.
- mConnectionRef.clear();
- }
-
- @Override
- public void openInputBridge(IBinder token, String name, int width,
- int height, int maxPointers) throws RemoteException {
- Connection connection = mConnectionRef.get();
- if (connection != null) {
- connection.openInputBridge(token, name, width, height, maxPointers);
- }
- }
-
- @Override
- public void closeInputBridge(IBinder token) throws RemoteException {
- Connection connection = mConnectionRef.get();
- if (connection != null) {
- connection.closeInputBridge(token);
- }
- }
-
- @Override
- public void clearInputBridge(IBinder token) throws RemoteException {
- Connection connection = mConnectionRef.get();
- if (connection != null) {
- connection.clearInputBridge(token);
- }
- }
-
- @Override
- public void sendTimestamp(IBinder token, long timestamp) throws RemoteException {
- Connection connection = mConnectionRef.get();
- if (connection != null) {
- connection.sendTimestamp(token, timestamp);
- }
- }
-
- @Override
- public void sendKeyDown(IBinder token, int keyCode) throws RemoteException {
- Connection connection = mConnectionRef.get();
- if (connection != null) {
- connection.sendKeyDown(token, keyCode);
- }
- }
-
- @Override
- public void sendKeyUp(IBinder token, int keyCode) throws RemoteException {
- Connection connection = mConnectionRef.get();
- if (connection != null) {
- connection.sendKeyUp(token, keyCode);
- }
- }
-
- @Override
- public void sendPointerDown(IBinder token, int pointerId, int x, int y)
- throws RemoteException {
- Connection connection = mConnectionRef.get();
- if (connection != null) {
- connection.sendPointerDown(token, pointerId, x, y);
- }
- }
-
- @Override
- public void sendPointerUp(IBinder token, int pointerId) throws RemoteException {
- Connection connection = mConnectionRef.get();
- if (connection != null) {
- connection.sendPointerUp(token, pointerId);
- }
- }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mConnected = false;
- @Override
- public void sendPointerSync(IBinder token) throws RemoteException {
- Connection connection = mConnectionRef.get();
- if (connection != null) {
- connection.sendPointerSync(token);
- }
+ if (DEBUG) {
+ Slog.d(TAG, this + ": onServiceDisconnected()");
}
}
}
diff --git a/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java b/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java
index 0d29edd02663..cddcabe80f33 100644
--- a/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java
+++ b/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java
@@ -41,27 +41,27 @@ import java.util.Collections;
*/
final class TvRemoteProviderWatcher {
- private static final String TAG = "TvRemoteProvWatcher"; // max. 23 chars
+ private static final String TAG = "TvRemoteProviderWatcher";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
private final Context mContext;
- private final TvRemoteProviderProxy.ProviderMethods mProvider;
private final Handler mHandler;
private final PackageManager mPackageManager;
private final ArrayList<TvRemoteProviderProxy> mProviderProxies = new ArrayList<>();
private final int mUserId;
private final String mUnbundledServicePackage;
+ private final Object mLock;
private boolean mRunning;
- TvRemoteProviderWatcher(Context context, TvRemoteProviderProxy.ProviderMethods provider) {
+ TvRemoteProviderWatcher(Context context, Object lock) {
mContext = context;
- mProvider = provider;
mHandler = new Handler(true);
mUserId = UserHandle.myUserId();
mPackageManager = context.getPackageManager();
mUnbundledServicePackage = context.getString(
com.android.internal.R.string.config_tvRemoteServicePackage);
+ mLock = lock;
}
public void start() {
@@ -116,7 +116,7 @@ final class TvRemoteProviderWatcher {
int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
if (sourceIndex < 0) {
TvRemoteProviderProxy providerProxy =
- new TvRemoteProviderProxy(mContext, mProvider,
+ new TvRemoteProviderProxy(mContext, mLock,
new ComponentName(serviceInfo.packageName, serviceInfo.name),
mUserId, serviceInfo.applicationInfo.uid);
providerProxy.start();
diff --git a/services/core/java/com/android/server/tv/TvRemoteService.java b/services/core/java/com/android/server/tv/TvRemoteService.java
index bee6fb34a899..58946456b940 100644
--- a/services/core/java/com/android/server/tv/TvRemoteService.java
+++ b/services/core/java/com/android/server/tv/TvRemoteService.java
@@ -17,17 +17,11 @@
package com.android.server.tv;
import android.content.Context;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
import android.util.Slog;
import com.android.server.SystemService;
import com.android.server.Watchdog;
-import java.io.IOException;
-import java.util.Map;
-
/**
* TvRemoteService represents a system service that allows a connected
* remote control (emote) service to inject white-listed input events
@@ -38,27 +32,17 @@ import java.util.Map;
public class TvRemoteService extends SystemService implements Watchdog.Monitor {
private static final String TAG = "TvRemoteService";
private static final boolean DEBUG = false;
- private static final boolean DEBUG_KEYS = false;
-
- private final TvRemoteProviderWatcher mWatcher;
- private Map<IBinder, UinputBridge> mBridgeMap = new ArrayMap();
/**
- * State guarded by mLock.
- * This is the second lock in sequence for an incoming call.
- * The first lock is always {@link TvRemoteProviderProxy#mLock}
- *
- * There are currently no methods that break this sequence.
- * Special note:
- * Outgoing call informInputBridgeConnected(), which is called from
- * openInputBridgeInternalLocked() uses a handler thereby relinquishing held locks.
+ * All actions on input bridges are serialized using mLock.
+ * This is necessary because {@link UInputBridge} is not thread-safe.
*/
private final Object mLock = new Object();
+ private final TvRemoteProviderWatcher mWatcher;
public TvRemoteService(Context context) {
super(context);
- mWatcher = new TvRemoteProviderWatcher(context,
- new UserProvider(TvRemoteService.this));
+ mWatcher = new TvRemoteProviderWatcher(context, mLock);
Watchdog.getInstance().addMonitor(this);
}
@@ -81,214 +65,4 @@ public class TvRemoteService extends SystemService implements Watchdog.Monitor {
mWatcher.start(); // Also schedules the start of all providers.
}
}
-
- private boolean openInputBridgeInternalLocked(final IBinder token,
- String name, int width, int height,
- int maxPointers) {
- if (DEBUG) {
- Slog.d(TAG, "openInputBridgeInternalLocked(), token: " + token + ", name: " + name +
- ", width: " + width + ", height: " + height + ", maxPointers: " + maxPointers);
- }
-
- try {
- //Create a new bridge, if one does not exist already
- if (mBridgeMap.containsKey(token)) {
- if (DEBUG) Slog.d(TAG, "RemoteBridge already exists");
- return true;
- }
-
- UinputBridge inputBridge = new UinputBridge(token, name, width, height, maxPointers);
- mBridgeMap.put(token, inputBridge);
-
- try {
- token.linkToDeath(new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- synchronized (mLock) {
- closeInputBridgeInternalLocked(token);
- }
- }
- }, 0);
- } catch (RemoteException e) {
- if (DEBUG) Slog.d(TAG, "Token is already dead");
- closeInputBridgeInternalLocked(token);
- return false;
- }
- } catch (IOException ioe) {
- Slog.e(TAG, "Cannot create device for " + name);
- return false;
- }
- return true;
- }
-
- private void closeInputBridgeInternalLocked(IBinder token) {
- if (DEBUG) {
- Slog.d(TAG, "closeInputBridgeInternalLocked(), token: " + token);
- }
-
- // Close an existing RemoteBridge
- UinputBridge inputBridge = mBridgeMap.get(token);
- if (inputBridge != null) {
- inputBridge.close(token);
- }
-
- mBridgeMap.remove(token);
- }
-
- private void clearInputBridgeInternalLocked(IBinder token) {
- if (DEBUG) {
- Slog.d(TAG, "clearInputBridgeInternalLocked(), token: " + token);
- }
-
- UinputBridge inputBridge = mBridgeMap.get(token);
- if (inputBridge != null) {
- inputBridge.clear(token);
- }
- }
-
- private void sendKeyDownInternalLocked(IBinder token, int keyCode) {
- if (DEBUG_KEYS) {
- Slog.d(TAG, "sendKeyDownInternalLocked(), token: " + token + ", keyCode: " + keyCode);
- }
-
- UinputBridge inputBridge = mBridgeMap.get(token);
- if (inputBridge != null) {
- inputBridge.sendKeyDown(token, keyCode);
- }
- }
-
- private void sendKeyUpInternalLocked(IBinder token, int keyCode) {
- if (DEBUG_KEYS) {
- Slog.d(TAG, "sendKeyUpInternalLocked(), token: " + token + ", keyCode: " + keyCode);
- }
-
- UinputBridge inputBridge = mBridgeMap.get(token);
- if (inputBridge != null) {
- inputBridge.sendKeyUp(token, keyCode);
- }
- }
-
- private void sendPointerDownInternalLocked(IBinder token, int pointerId, int x, int y) {
- if (DEBUG_KEYS) {
- Slog.d(TAG, "sendPointerDownInternalLocked(), token: " + token + ", pointerId: " +
- pointerId + ", x: " + x + ", y: " + y);
- }
-
- UinputBridge inputBridge = mBridgeMap.get(token);
- if (inputBridge != null) {
- inputBridge.sendPointerDown(token, pointerId, x, y);
- }
- }
-
- private void sendPointerUpInternalLocked(IBinder token, int pointerId) {
- if (DEBUG_KEYS) {
- Slog.d(TAG, "sendPointerUpInternalLocked(), token: " + token + ", pointerId: " +
- pointerId);
- }
-
- UinputBridge inputBridge = mBridgeMap.get(token);
- if (inputBridge != null) {
- inputBridge.sendPointerUp(token, pointerId);
- }
- }
-
- private void sendPointerSyncInternalLocked(IBinder token) {
- if (DEBUG_KEYS) {
- Slog.d(TAG, "sendPointerSyncInternalLocked(), token: " + token);
- }
-
- UinputBridge inputBridge = mBridgeMap.get(token);
- if (inputBridge != null) {
- inputBridge.sendPointerSync(token);
- }
- }
-
- private final class UserProvider implements TvRemoteProviderProxy.ProviderMethods {
-
- private final TvRemoteService mService;
-
- public UserProvider(TvRemoteService service) {
- mService = service;
- }
-
- @Override
- public boolean openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name,
- int width, int height, int maxPointers) {
- if (DEBUG) {
- Slog.d(TAG, "openInputBridge(), token: " + token +
- ", name: " + name + ", width: " + width +
- ", height: " + height + ", maxPointers: " + maxPointers);
- }
-
- synchronized (mLock) {
- return mService.openInputBridgeInternalLocked(token, name, width,
- height, maxPointers);
- }
- }
-
- @Override
- public void closeInputBridge(TvRemoteProviderProxy provider, IBinder token) {
- if (DEBUG) Slog.d(TAG, "closeInputBridge(), token: " + token);
- synchronized (mLock) {
- mService.closeInputBridgeInternalLocked(token);
- }
- }
-
- @Override
- public void clearInputBridge(TvRemoteProviderProxy provider, IBinder token) {
- if (DEBUG) Slog.d(TAG, "clearInputBridge(), token: " + token);
- synchronized (mLock) {
- mService.clearInputBridgeInternalLocked(token);
- }
- }
-
- @Override
- public void sendKeyDown(TvRemoteProviderProxy provider, IBinder token, int keyCode) {
- if (DEBUG_KEYS) {
- Slog.d(TAG, "sendKeyDown(), token: " + token + ", keyCode: " + keyCode);
- }
- synchronized (mLock) {
- mService.sendKeyDownInternalLocked(token, keyCode);
- }
- }
-
- @Override
- public void sendKeyUp(TvRemoteProviderProxy provider, IBinder token, int keyCode) {
- if (DEBUG_KEYS) {
- Slog.d(TAG, "sendKeyUp(), token: " + token + ", keyCode: " + keyCode);
- }
- synchronized (mLock) {
- mService.sendKeyUpInternalLocked(token, keyCode);
- }
- }
-
- @Override
- public void sendPointerDown(TvRemoteProviderProxy provider, IBinder token, int pointerId,
- int x, int y) {
- if (DEBUG_KEYS) {
- Slog.d(TAG, "sendPointerDown(), token: " + token + ", pointerId: " + pointerId);
- }
- synchronized (mLock) {
- mService.sendPointerDownInternalLocked(token, pointerId, x, y);
- }
- }
-
- @Override
- public void sendPointerUp(TvRemoteProviderProxy provider, IBinder token, int pointerId) {
- if (DEBUG_KEYS) {
- Slog.d(TAG, "sendPointerUp(), token: " + token + ", pointerId: " + pointerId);
- }
- synchronized (mLock) {
- mService.sendPointerUpInternalLocked(token, pointerId);
- }
- }
-
- @Override
- public void sendPointerSync(TvRemoteProviderProxy provider, IBinder token) {
- if (DEBUG_KEYS) Slog.d(TAG, "sendPointerSync(), token: " + token);
- synchronized (mLock) {
- mService.sendPointerSyncInternalLocked(token);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/tv/TvRemoteServiceInput.java b/services/core/java/com/android/server/tv/TvRemoteServiceInput.java
new file mode 100644
index 000000000000..8fe6da5e8dbe
--- /dev/null
+++ b/services/core/java/com/android/server/tv/TvRemoteServiceInput.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2019 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.tv;
+
+import android.media.tv.ITvRemoteProvider;
+import android.media.tv.ITvRemoteServiceInput;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.util.Map;
+
+final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
+ private static final String TAG = "TvRemoteServiceInput";
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_KEYS = false;
+
+ private final Map<IBinder, UinputBridge> mBridgeMap;
+ private final Object mLock;
+ private final ITvRemoteProvider mProvider;
+
+ TvRemoteServiceInput(Object lock, ITvRemoteProvider provider) {
+ mBridgeMap = new ArrayMap();
+ mLock = lock;
+ mProvider = provider;
+ }
+
+ @Override
+ public void openInputBridge(IBinder token, String name, int width,
+ int height, int maxPointers) {
+ if (DEBUG) {
+ Slog.d(TAG, "openInputBridge(), token: " + token
+ + ", name: " + name + ", width: " + width
+ + ", height: " + height + ", maxPointers: " + maxPointers);
+ }
+
+ synchronized (mLock) {
+ if (mBridgeMap.containsKey(token)) {
+ if (DEBUG) {
+ Slog.d(TAG, "InputBridge already exists");
+ }
+ } else {
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ mBridgeMap.put(token,
+ new UinputBridge(token, name, width, height, maxPointers));
+ token.linkToDeath(new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ closeInputBridge(token);
+ }
+ }, 0);
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot create device for " + name);
+ return;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Token is already dead");
+ closeInputBridge(token);
+ return;
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
+
+ try {
+ mProvider.onInputBridgeConnected(token);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed remote call to onInputBridgeConnected");
+ }
+ }
+
+ @Override
+ public void closeInputBridge(IBinder token) {
+ if (DEBUG) {
+ Slog.d(TAG, "closeInputBridge(), token: " + token);
+ }
+
+ synchronized (mLock) {
+ UinputBridge inputBridge = mBridgeMap.remove(token);
+ if (inputBridge == null) {
+ return;
+ }
+
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ inputBridge.close(token);
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
+
+ @Override
+ public void clearInputBridge(IBinder token) {
+ if (DEBUG) {
+ Slog.d(TAG, "clearInputBridge, token: " + token);
+ }
+
+ synchronized (mLock) {
+ UinputBridge inputBridge = mBridgeMap.get(token);
+ if (inputBridge == null) {
+ return;
+ }
+
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ inputBridge.clear(token);
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
+
+ @Override
+ public void sendTimestamp(IBinder token, long timestamp) {
+ if (DEBUG) {
+ Slog.e(TAG, "sendTimestamp is deprecated, please remove all usages of this API.");
+ }
+ }
+
+ @Override
+ public void sendKeyDown(IBinder token, int keyCode) {
+ if (DEBUG_KEYS) {
+ Slog.d(TAG, "sendKeyDown(), token: " + token + ", keyCode: " + keyCode);
+ }
+
+ synchronized (mLock) {
+ UinputBridge inputBridge = mBridgeMap.get(token);
+ if (inputBridge == null) {
+ return;
+ }
+
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ inputBridge.sendKeyDown(token, keyCode);
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
+
+ @Override
+ public void sendKeyUp(IBinder token, int keyCode) {
+ if (DEBUG_KEYS) {
+ Slog.d(TAG, "sendKeyUp(), token: " + token + ", keyCode: " + keyCode);
+ }
+
+ synchronized (mLock) {
+ UinputBridge inputBridge = mBridgeMap.get(token);
+ if (inputBridge == null) {
+ return;
+ }
+
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ inputBridge.sendKeyUp(token, keyCode);
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
+
+ @Override
+ public void sendPointerDown(IBinder token, int pointerId, int x, int y) {
+ if (DEBUG_KEYS) {
+ Slog.d(TAG, "sendPointerDown(), token: " + token + ", pointerId: "
+ + pointerId + ", x: " + x + ", y: " + y);
+ }
+
+ synchronized (mLock) {
+ UinputBridge inputBridge = mBridgeMap.get(token);
+ if (inputBridge == null) {
+ return;
+ }
+
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ inputBridge.sendPointerDown(token, pointerId, x, y);
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
+
+ @Override
+ public void sendPointerUp(IBinder token, int pointerId) {
+ if (DEBUG_KEYS) {
+ Slog.d(TAG, "sendPointerUp(), token: " + token + ", pointerId: " + pointerId);
+ }
+
+ synchronized (mLock) {
+ UinputBridge inputBridge = mBridgeMap.get(token);
+ if (inputBridge == null) {
+ return;
+ }
+
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ inputBridge.sendPointerUp(token, pointerId);
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
+
+ @Override
+ public void sendPointerSync(IBinder token) {
+ if (DEBUG_KEYS) {
+ Slog.d(TAG, "sendPointerSync(), token: " + token);
+ }
+
+ synchronized (mLock) {
+ UinputBridge inputBridge = mBridgeMap.get(token);
+ if (inputBridge == null) {
+ return;
+ }
+
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ inputBridge.sendPointerSync(token);
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index dbf06a580282..54bb5f7bcd5f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1743,7 +1743,12 @@ class ActivityStarter {
return START_SUCCESS;
}
- if (!mMovedToFront && mDoResume) {
+ if (mMovedToFront) {
+ // We moved the task to front, use starting window to hide initial drawn delay.
+ targetTaskTop.showStartingWindow(null /* prev */, false /* newTask */,
+ true /* taskSwitch */);
+ } else if (mDoResume) {
+ // Make sure the stack and its belonging display are moved to topmost.
mTargetStack.moveToFront("intentActivityFound");
}
// We didn't do anything... but it was needed (a.k.a., client don't use that intent!)
@@ -2349,11 +2354,6 @@ class ActivityStarter {
}
mOptions = null;
-
- // We are moving a task to the front, use starting window to hide initial drawn
- // delay.
- intentActivity.showStartingWindow(null /* prev */, false /* newTask */,
- true /* taskSwitch */);
}
}
// Need to update mTargetStack because if task was moved out of it, the original stack may
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index d5fbd2b316e7..7aa9c7cce876 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -56,37 +56,57 @@ inline Return<R> NullptrStatus() {
return Return<R>{Status::fromExceptionCode(Status::EX_NULL_POINTER)};
}
-// Helper used to transparently deal with the vibrator HAL becoming unavailable.
-template<class R, class I, class... Args0, class... Args1>
-Return<R> halCall(Return<R> (I::* fn)(Args0...), Args1&&... args1) {
- // Assume that if getService returns a nullptr, HAL is not available on the
- // device.
- static sp<I> sHal = I::getService();
- static bool sAvailable = sHal != nullptr;
+template <typename I>
+class HalWrapper {
+ public:
+ static std::unique_ptr<HalWrapper> Create() {
+ // Assume that if getService returns a nullptr, HAL is not available on the
+ // device.
+ auto hal = I::getService();
+ return hal ? std::unique_ptr<HalWrapper>(new HalWrapper(std::move(hal))) : nullptr;
+ }
- if (!sAvailable) {
- return NullptrStatus<R>();
+ // Helper used to transparently deal with the vibrator HAL becoming unavailable.
+ template<class R, class... Args0, class... Args1>
+ Return<R> call(Return<R> (I::* fn)(Args0...), Args1&&... args1) {
+ // Return<R> doesn't have a default constructor, so make a Return<R> with
+ // STATUS::EX_NONE.
+ using ::android::hardware::Status;
+ Return<R> ret{Status::fromExceptionCode(Status::EX_NONE)};
+
+ // Note that ret is guaranteed to be changed after this loop.
+ for (int i = 0; i < NUM_TRIES; ++i) {
+ ret = (mHal == nullptr) ? NullptrStatus<R>()
+ : (*mHal.*fn)(std::forward<Args1>(args1)...);
+
+ if (ret.isOk()) {
+ break;
+ }
+
+ ALOGE("Failed to issue command to vibrator HAL. Retrying.");
+ // Restoring connection to the HAL.
+ mHal = I::tryGetService();
+ }
+ return ret;
}
- // Return<R> doesn't have a default constructor, so make a Return<R> with
- // STATUS::EX_NONE.
- using ::android::hardware::Status;
- Return<R> ret{Status::fromExceptionCode(Status::EX_NONE)};
+ private:
+ HalWrapper(sp<I> &&hal) : mHal(std::move(hal)) {}
- // Note that ret is guaranteed to be changed after this loop.
- for (int i = 0; i < NUM_TRIES; ++i) {
- ret = (sHal == nullptr) ? NullptrStatus<R>()
- : (*sHal.*fn)(std::forward<Args1>(args1)...);
+ private:
+ sp<I> mHal;
+};
- if (ret.isOk()) {
- break;
- }
+template <typename I>
+static auto getHal() {
+ static auto sHalWrapper = HalWrapper<I>::Create();
+ return sHalWrapper.get();
+}
- ALOGE("Failed to issue command to vibrator HAL. Retrying.");
- // Restoring connection to the HAL.
- sHal = I::tryGetService();
- }
- return ret;
+template<class R, class I, class... Args0, class... Args1>
+Return<R> halCall(Return<R> (I::* fn)(Args0...), Args1&&... args1) {
+ auto hal = getHal<I>();
+ return hal ? hal->call(fn, std::forward<Args1>(args1)...) : NullptrStatus<R>();
}
template<class R>
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 03f475582a5a..0e9da83d4d1f 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -159,12 +159,7 @@ static void loadSystemIconAsSpriteWithPointerIcon(JNIEnv* env, jobject contextOb
status_t status = android_view_PointerIcon_loadSystemIcon(env,
contextObj, style, outPointerIcon);
if (!status) {
- SkBitmap* bitmapCopy = &outSpriteIcon->bitmap;
- SkImageInfo bitmapCopyInfo = outPointerIcon->bitmap.info().makeColorType(kN32_SkColorType);
- if (bitmapCopy->tryAllocPixels(bitmapCopyInfo)) {
- outPointerIcon->bitmap.readPixels(bitmapCopy->info(), bitmapCopy->getPixels(),
- bitmapCopy->rowBytes(), 0, 0);
- }
+ outSpriteIcon->bitmap = outPointerIcon->bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888);
outSpriteIcon->style = outPointerIcon->style;
outSpriteIcon->hotSpotX = outPointerIcon->hotSpotX;
outSpriteIcon->hotSpotY = outPointerIcon->hotSpotY;
@@ -1709,15 +1704,8 @@ static void nativeSetCustomPointerIcon(JNIEnv* env, jclass /* clazz */,
return;
}
- SpriteIcon spriteIcon;
- SkImageInfo spriteInfo = pointerIcon.bitmap.info().makeColorType(kN32_SkColorType);
- if (spriteIcon.bitmap.tryAllocPixels(spriteInfo)) {
- pointerIcon.bitmap.readPixels(spriteInfo, spriteIcon.bitmap.getPixels(),
- spriteIcon.bitmap.rowBytes(), 0, 0);
- }
- spriteIcon.style = pointerIcon.style;
- spriteIcon.hotSpotX = pointerIcon.hotSpotX;
- spriteIcon.hotSpotY = pointerIcon.hotSpotY;
+ SpriteIcon spriteIcon(pointerIcon.bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888),
+ pointerIcon.style, pointerIcon.hotSpotX, pointerIcon.hotSpotY);
im->setCustomPointerIcon(spriteIcon);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index fe3e4b12c488..479dd1e5d84e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8052,6 +8052,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
"clearDeviceOwner can only be called by the device owner");
}
enforceUserUnlocked(deviceOwnerUserId);
+ DevicePolicyData policy = getUserData(deviceOwnerUserId);
+ if (policy.mPasswordTokenHandle != 0) {
+ mLockPatternUtils.removeEscrowToken(policy.mPasswordTokenHandle, deviceOwnerUserId);
+ }
final ActiveAdmin admin = getDeviceOwnerAdminLocked();
long ident = mInjector.binderClearCallingIdentity();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index d90091017116..a25e40f8cc13 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1212,6 +1212,45 @@ public class DevicePolicyManagerTest extends DpmTestBase {
assertTrue(dpm.isDeviceManaged());
}
+ /**
+ * Test for: {@link DevicePolicyManager#clearDeviceOwnerApp(String)}
+ *
+ * Validates that when the device owner is removed, the reset password token is cleared
+ */
+ public void testClearDeviceOwner_clearResetPasswordToken() throws Exception {
+ mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+ mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+
+ // Install admin1 on system user
+ setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+
+ // Set admin1 to active admin and device owner
+ dpm.setActiveAdmin(admin1, /* replace =*/ false);
+ dpm.setDeviceOwner(admin1, null, UserHandle.USER_SYSTEM);
+
+ // Add reset password token
+ final long handle = 12000;
+ final byte[] token = new byte[32];
+ when(getServices().lockPatternUtils.addEscrowToken(eq(token), eq(UserHandle.USER_SYSTEM),
+ nullable(EscrowTokenStateChangeCallback.class)))
+ .thenReturn(handle);
+ assertTrue(dpm.setResetPasswordToken(admin1, token));
+
+ // Assert reset password token is active
+ when(getServices().lockPatternUtils.isEscrowTokenActive(eq(handle),
+ eq(UserHandle.USER_SYSTEM)))
+ .thenReturn(true);
+ assertTrue(dpm.isResetPasswordTokenActive(admin1));
+
+ // Remove the device owner
+ dpm.clearDeviceOwnerApp(admin1.getPackageName());
+
+ // Verify password reset password token was removed
+ verify(getServices().lockPatternUtils).removeEscrowToken(eq(handle),
+ eq(UserHandle.USER_SYSTEM));
+ }
+
public void testSetProfileOwner() throws Exception {
setAsProfileOwner(admin1);
diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
index 0b8c2a55b45e..0c5451f4ec31 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
@@ -16,7 +16,6 @@
package com.android.server.rollback;
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -271,15 +270,4 @@ public class AppDataRollbackHelperTest {
inOrder.verifyNoMoreInteractions();
}
-
- @Test
- public void snapshotAddDataSavesSnapshottedUsersToInfo() {
- Installer installer = mock(Installer.class);
- AppDataRollbackHelper helper = new AppDataRollbackHelper(installer);
-
- PackageRollbackInfo info = createPackageRollbackInfo("com.foo.bar");
- helper.snapshotAppData(5, info, new int[]{10, 11});
-
- assertArrayEquals(info.getSnapshottedUsers().toArray(), new int[]{10, 11});
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
index b5925a6e750f..151b6e2890bb 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -18,22 +18,48 @@ package com.android.server.rollback;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.util.IntArray;
import android.util.SparseLongArray;
+import com.google.common.collect.Range;
+
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.io.File;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
@RunWith(JUnit4.class)
public class RollbackUnitTest {
+ private static final String PKG_1 = "test.testpackage.pkg1";
+ private static final String PKG_2 = "test.testpackage.pkg2";
+ private static final String PKG_3 = "com.blah.hello.three";
+ private static final String PKG_4 = "com.something.4pack";
+
+ @Mock private AppDataRollbackHelper mMockDataHelper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
@Test
public void newEmptyStagedRollbackDefaults() {
int rollbackId = 123;
@@ -61,82 +87,229 @@ public class RollbackUnitTest {
}
@Test
- public void rollbackStateChanges() {
+ public void rollbackMadeAvailable() {
Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
assertThat(rollback.isEnabling()).isTrue();
assertThat(rollback.isAvailable()).isFalse();
assertThat(rollback.isCommitted()).isFalse();
- rollback.setAvailable();
+ Instant availableTime = Instant.now();
+ rollback.makeAvailable();
assertThat(rollback.isEnabling()).isFalse();
assertThat(rollback.isAvailable()).isTrue();
assertThat(rollback.isCommitted()).isFalse();
- rollback.setCommitted();
+ assertThat(rollback.getTimestamp()).isIn(Range.closed(availableTime, Instant.now()));
+ }
+
+ @Test
+ public void deletedRollbackCannotBeMadeAvailable() {
+ Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+
+ rollback.delete(mMockDataHelper);
+
+ assertThat(rollback.isDeleted()).isTrue();
+
+ rollback.makeAvailable();
- assertThat(rollback.isEnabling()).isFalse();
assertThat(rollback.isAvailable()).isFalse();
- assertThat(rollback.isCommitted()).isTrue();
+ assertThat(rollback.isDeleted()).isTrue();
}
@Test
public void getPackageNamesAllAndJustApex() {
- String pkg1 = "test.testpackage.pkg1";
- String pkg2 = "test.testpackage.pkg2";
- String pkg3 = "com.blah.hello.three";
- String pkg4 = "com.something.4pack";
-
Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
- PackageRollbackInfo pkgInfo1 = pkgInfoFor(pkg1, 12, 10, false);
- PackageRollbackInfo pkgInfo2 = pkgInfoFor(pkg2, 12, 10, true);
- PackageRollbackInfo pkgInfo3 = pkgInfoFor(pkg3, 12, 10, false);
- PackageRollbackInfo pkgInfo4 = pkgInfoFor(pkg4, 12, 10, true);
+ PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false);
+ PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 11, true);
+ PackageRollbackInfo pkgInfo3 = newPkgInfoFor(PKG_3, 19, 1, false);
+ PackageRollbackInfo pkgInfo4 = newPkgInfoFor(PKG_4, 12, 1, true);
rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2, pkgInfo3, pkgInfo4));
- assertThat(rollback.getPackageNames()).containsExactly(pkg1, pkg2, pkg3, pkg4);
- assertThat(rollback.getApexPackageNames()).containsExactly(pkg2, pkg4);
+ assertThat(rollback.getPackageNames()).containsExactly(PKG_1, PKG_2, PKG_3, PKG_4);
+ assertThat(rollback.getApexPackageNames()).containsExactly(PKG_2, PKG_4);
}
@Test
- public void includesPackages() {
- String pkg1 = "test.testpackage.pkg1";
- String pkg2 = "test.testpackage.pkg2";
- String pkg3 = "com.blah.hello.three";
- String pkg4 = "com.something.4pack";
-
+ public void includesPackagesAfterEnable() {
Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
- PackageRollbackInfo pkgInfo1 = pkgInfoFor(pkg1, 12, 10, false);
- PackageRollbackInfo pkgInfo2 = pkgInfoFor(pkg2, 18, 12, true);
- PackageRollbackInfo pkgInfo3 = pkgInfoFor(pkg3, 157, 156, false);
- PackageRollbackInfo pkgInfo4 = pkgInfoFor(pkg4, 99, 1, true);
+ PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false);
+ PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true);
+ PackageRollbackInfo pkgInfo3 = newPkgInfoFor(PKG_3, 157, 156, false);
+ PackageRollbackInfo pkgInfo4 = newPkgInfoFor(PKG_4, 99, 1, true);
rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2, pkgInfo3, pkgInfo4));
- assertThat(rollback.includesPackage(pkg2)).isTrue();
- assertThat(rollback.includesPackage(pkg3)).isTrue();
+ assertThat(rollback.includesPackage(PKG_2)).isTrue();
+ assertThat(rollback.includesPackage(PKG_3)).isTrue();
assertThat(rollback.includesPackage("com.something.else")).isFalse();
- assertThat(rollback.includesPackageWithDifferentVersion(pkg1, 12)).isFalse();
- assertThat(rollback.includesPackageWithDifferentVersion(pkg1, 1)).isTrue();
+ assertThat(rollback.includesPackageWithDifferentVersion(PKG_1, 12)).isFalse();
+ assertThat(rollback.includesPackageWithDifferentVersion(PKG_1, 1)).isTrue();
- assertThat(rollback.includesPackageWithDifferentVersion(pkg2, 18)).isFalse();
- assertThat(rollback.includesPackageWithDifferentVersion(pkg2, 12)).isTrue();
+ assertThat(rollback.includesPackageWithDifferentVersion(PKG_2, 18)).isFalse();
+ assertThat(rollback.includesPackageWithDifferentVersion(PKG_2, 12)).isTrue();
- assertThat(rollback.includesPackageWithDifferentVersion(pkg3, 157)).isFalse();
- assertThat(rollback.includesPackageWithDifferentVersion(pkg3, 156)).isTrue();
- assertThat(rollback.includesPackageWithDifferentVersion(pkg3, 15)).isTrue();
+ assertThat(rollback.includesPackageWithDifferentVersion(PKG_3, 157)).isFalse();
+ assertThat(rollback.includesPackageWithDifferentVersion(PKG_3, 156)).isTrue();
+ assertThat(rollback.includesPackageWithDifferentVersion(PKG_3, 15)).isTrue();
- assertThat(rollback.includesPackageWithDifferentVersion(pkg4, 99)).isFalse();
- assertThat(rollback.includesPackageWithDifferentVersion(pkg4, 100)).isTrue();
+ assertThat(rollback.includesPackageWithDifferentVersion(PKG_4, 99)).isFalse();
+ assertThat(rollback.includesPackageWithDifferentVersion(PKG_4, 100)).isTrue();
}
- private static PackageRollbackInfo pkgInfoFor(
+ @Test
+ public void snapshotWhenEnabling() {
+ Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+ PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false);
+ PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true);
+ rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2));
+
+ assertThat(rollback.isEnabling()).isTrue();
+
+ int[] userIds = {4, 77};
+ rollback.snapshotUserData(PKG_2, userIds, mMockDataHelper);
+
+ // Data is snapshotted for the specified package.
+ verify(mMockDataHelper).snapshotAppData(eq(123), pkgRollbackInfoFor(PKG_2), eq(userIds));
+ verify(mMockDataHelper, never())
+ .snapshotAppData(anyInt(), pkgRollbackInfoFor(PKG_1), any());
+ }
+
+ @Test
+ public void snapshotWhenAvailable() {
+ Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+ PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false);
+ PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true);
+ rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2));
+
+ rollback.makeAvailable();
+
+ assertThat(rollback.isAvailable()).isTrue();
+
+ int[] userIds = {4, 77};
+ rollback.snapshotUserData(PKG_2, userIds, mMockDataHelper);
+
+ // No data is snapshotted as rollback was not in the enabling state.
+ verify(mMockDataHelper, never())
+ .snapshotAppData(anyInt(), pkgRollbackInfoFor(PKG_1), any());
+ verify(mMockDataHelper, never())
+ .snapshotAppData(anyInt(), pkgRollbackInfoFor(PKG_2), any());
+ }
+
+ @Test
+ public void snapshotWhenDeleted() {
+ Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+ PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false);
+ PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true);
+ rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2));
+
+ rollback.delete(mMockDataHelper);
+
+ assertThat(rollback.isDeleted()).isTrue();
+
+ int[] userIds = {4, 77};
+ rollback.snapshotUserData(PKG_2, userIds, mMockDataHelper);
+
+ // No data is snapshotted as rollback was not in the enabling state.
+ verify(mMockDataHelper, never())
+ .snapshotAppData(anyInt(), pkgRollbackInfoFor(PKG_1), any());
+ verify(mMockDataHelper, never())
+ .snapshotAppData(anyInt(), pkgRollbackInfoFor(PKG_2), any());
+ }
+
+ @Test
+ public void snapshotThenDelete() {
+ Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+ PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false);
+ PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true);
+ rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2));
+
+ int[] userIds = {12, 18};
+ rollback.snapshotUserData(PKG_2, userIds, mMockDataHelper);
+
+ verify(mMockDataHelper).snapshotAppData(eq(123), pkgRollbackInfoFor(PKG_2), eq(userIds));
+
+ rollback.delete(mMockDataHelper);
+
+ verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(12));
+ verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(18));
+
+ assertThat(rollback.isDeleted()).isTrue();
+ }
+
+ @Test
+ public void restoreUserDataDoesNothingIfNotInProgress() {
+ Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+ PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false);
+ PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true);
+ rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2));
+
+ assertThat(rollback.isRestoreUserDataInProgress()).isFalse();
+
+ assertThat(rollback.restoreUserDataForPackageIfInProgress(
+ PKG_1, new int[] { 5 }, 333, "", mMockDataHelper)).isFalse();
+
+ verify(mMockDataHelper, never()).restoreAppData(anyInt(), any(), anyInt(), anyInt(), any());
+ }
+
+ @Test
+ public void restoreUserDataDoesNothingIfPackageNotFound() {
+ Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+ PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false);
+ PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true);
+ rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2));
+
+ rollback.setRestoreUserDataInProgress(true);
+ assertThat(rollback.isRestoreUserDataInProgress()).isTrue();
+
+ assertThat(rollback.restoreUserDataForPackageIfInProgress(
+ PKG_3, new int[] { 5 }, 333, "", mMockDataHelper)).isFalse();
+
+ verify(mMockDataHelper, never()).restoreAppData(anyInt(), any(), anyInt(), anyInt(), any());
+ }
+
+ @Test
+ public void restoreUserDataRestoresIfInProgressAndPackageFound() {
+ Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+ PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false);
+ PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true);
+ rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2));
+
+ rollback.setRestoreUserDataInProgress(true);
+ assertThat(rollback.isRestoreUserDataInProgress()).isTrue();
+
+ assertThat(rollback.restoreUserDataForPackageIfInProgress(
+ PKG_1, new int[] { 5, 7 }, 333, "blah", mMockDataHelper)).isTrue();
+
+ verify(mMockDataHelper).restoreAppData(123, pkgInfo1, 5, 333, "blah");
+ verify(mMockDataHelper).restoreAppData(123, pkgInfo1, 7, 333, "blah");
+ }
+
+ private static PackageRollbackInfo newPkgInfoFor(
String packageName, long fromVersion, long toVersion, boolean isApex) {
return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion),
new VersionedPackage(packageName, toVersion),
new IntArray(), new ArrayList<>(), isApex, new IntArray(), new SparseLongArray());
}
+
+ private static class PackageRollbackInfoForPackage implements
+ ArgumentMatcher<PackageRollbackInfo> {
+ private final String mPkg;
+
+ PackageRollbackInfoForPackage(String pkg) {
+ mPkg = pkg;
+ }
+
+ @Override
+ public boolean matches(PackageRollbackInfo pkgRollbackInfo) {
+ return pkgRollbackInfo.getPackageName().equals(mPkg);
+ }
+ }
+
+ private static PackageRollbackInfo pkgRollbackInfoFor(String pkg) {
+ return argThat(new PackageRollbackInfoForPackage(pkg));
+ }
}
diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml
index 9b73abfd6908..91fb7c12b392 100644
--- a/tests/FlickerTests/AndroidManifest.xml
+++ b/tests/FlickerTests/AndroidManifest.xml
@@ -21,6 +21,8 @@
<!-- Read and write traces from external storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <!-- Write secure settings -->
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<!-- Capture screen contents -->
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
<!-- Enable / Disable tracing !-->
diff --git a/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt
index cb2950802c5d..2e48d97d6349 100644
--- a/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt
@@ -20,7 +20,6 @@ import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.ImportDeclaration
import com.github.javaparser.ast.expr.BinaryExpr
import com.github.javaparser.ast.expr.Expression
-import com.github.javaparser.ast.expr.MethodCallExpr
import com.github.javaparser.ast.expr.StringLiteralExpr
object CodeUtils {
@@ -74,9 +73,8 @@ object CodeUtils {
}
}
- fun getPositionString(call: MethodCallExpr, fileName: String): String {
+ fun getPositionString(fileName: String): String {
return when {
- call.range.isPresent -> "$fileName:${call.range.get().begin.line}"
else -> fileName
}
}
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index 3f38bc01fc7c..c2964a3dad7e 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -73,7 +73,7 @@ class SourceTransformer(
}
val ifStmt: IfStmt
if (group.enabled) {
- val position = CodeUtils.getPositionString(call, fileName)
+ val position = CodeUtils.getPositionString(fileName)
val hash = CodeUtils.hash(position, messageString, level, group)
val newCall = call.clone()
if (!group.textEnabled) {
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
index 4c4179768b7f..172de0e9d4d0 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
@@ -32,7 +32,7 @@ class ViewerConfigBuilder(
group: LogGroup
) {
if (group.enabled) {
- val position = CodeUtils.getPositionString(call, fileName)
+ val position = CodeUtils.getPositionString(fileName)
val key = CodeUtils.hash(position, messageString, level, group)
if (statements.containsKey(key)) {
if (statements[key] != LogCall(messageString, level, group, position)) {
diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
index f221fbd216b9..18504b684006 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -78,7 +78,7 @@ class SourceTransformerTest {
class Test {
void test() {
- if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1922613844, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -88,7 +88,7 @@ class SourceTransformerTest {
class Test {
void test() {
- if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 805272208, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2);
+ if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 1780316587, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2);
}
}
@@ -100,8 +100,8 @@ class SourceTransformerTest {
class Test {
void test() {
- if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1922613844, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1922613844, 9, "test %d %f", protoLogParam0, protoLogParam1); }
- if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -154595499, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -111,7 +111,7 @@ class SourceTransformerTest {
class Test {
void test() {
- if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { org.example.ProtoLogImpl.w(TEST_GROUP, 1913810354, 0, "test", (Object[]) null); }
+ if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { org.example.ProtoLogImpl.w(TEST_GROUP, -1741986185, 0, "test", (Object[]) null); }
}
}
""".trimIndent()
@@ -121,7 +121,7 @@ class SourceTransformerTest {
class Test {
void test() {
- if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1922613844, 9, null, protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, null, protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -131,7 +131,7 @@ class SourceTransformerTest {
class Test {
void test() {
- if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 805272208, 9, null, protoLogParam0, protoLogParam1, protoLogParam2);
+ if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 1780316587, 9, null, protoLogParam0, protoLogParam1, protoLogParam2);
}
}
@@ -199,7 +199,7 @@ class SourceTransformerTest {
assertEquals("w", methodCall.name.asString())
assertEquals(6, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
- assertEquals("1922613844", methodCall.arguments[1].toString())
+ assertEquals("1698911065", methodCall.arguments[1].toString())
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
assertEquals("protoLogParam0", methodCall.arguments[4].toString())
@@ -241,7 +241,7 @@ class SourceTransformerTest {
assertEquals("w", methodCall.name.asString())
assertEquals(6, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
- assertEquals("1922613844", methodCall.arguments[1].toString())
+ assertEquals("1698911065", methodCall.arguments[1].toString())
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
assertEquals("protoLogParam0", methodCall.arguments[4].toString())
@@ -279,7 +279,7 @@ class SourceTransformerTest {
assertEquals("w", methodCall.name.asString())
assertEquals(7, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
- assertEquals("805272208", methodCall.arguments[1].toString())
+ assertEquals("1780316587", methodCall.arguments[1].toString())
assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
assertEquals("protoLogParam0", methodCall.arguments[4].toString())
assertEquals("protoLogParam1", methodCall.arguments[5].toString())
@@ -316,7 +316,7 @@ class SourceTransformerTest {
assertEquals("w", methodCall.name.asString())
assertEquals(5, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
- assertEquals("1913810354", methodCall.arguments[1].toString())
+ assertEquals("-1741986185", methodCall.arguments[1].toString())
assertEquals(0.toString(), methodCall.arguments[2].toString())
assertEquals(TRANSFORMED_CODE_NO_PARAMS, out)
}
@@ -350,7 +350,7 @@ class SourceTransformerTest {
assertEquals("w", methodCall.name.asString())
assertEquals(6, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
- assertEquals("1922613844", methodCall.arguments[1].toString())
+ assertEquals("1698911065", methodCall.arguments[1].toString())
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
assertEquals("null", methodCall.arguments[3].toString())
assertEquals("protoLogParam0", methodCall.arguments[4].toString())
@@ -388,7 +388,7 @@ class SourceTransformerTest {
assertEquals("w", methodCall.name.asString())
assertEquals(7, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
- assertEquals("805272208", methodCall.arguments[1].toString())
+ assertEquals("1780316587", methodCall.arguments[1].toString())
assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
assertEquals("null", methodCall.arguments[3].toString())
assertEquals("protoLogParam0", methodCall.arguments[4].toString())
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index eb5a717d2b07..2afb14a12110 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -1056,26 +1056,6 @@ public class WifiConfiguration implements Parcelable {
}
/**
- * @hide
- * Returns Randomized MAC address to use with the network.
- * If it is not set/valid, creates a new randomized address.
- * If it can't generate a valid mac, returns the default MAC.
- */
- public @NonNull MacAddress getOrCreateRandomizedMacAddress() {
- int randomMacGenerationCount = 0;
- while (!isValidMacAddressForRandomization(mRandomizedMacAddress)
- && randomMacGenerationCount < MAXIMUM_RANDOM_MAC_GENERATION_RETRY) {
- mRandomizedMacAddress = MacAddress.createRandomUnicastAddress();
- randomMacGenerationCount++;
- }
-
- if (!isValidMacAddressForRandomization(mRandomizedMacAddress)) {
- mRandomizedMacAddress = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
- }
- return mRandomizedMacAddress;
- }
-
- /**
* Returns MAC address set to be the local randomized MAC address.
* Depending on user preference, the device may or may not use the returned MAC address for
* connections to this network.
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 075531ce158e..68948cbbe7a9 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -17,6 +17,7 @@
package android.net.wifi;
import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -58,13 +59,23 @@ public class WifiScanner {
/** 5 GHz band excluding DFS channels */
public static final int WIFI_BAND_5_GHZ = 2; /* 5 GHz band without DFS channels */
/** DFS channels from 5 GHz band only */
- public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 4; /* 5 GHz band with DFS channels */
+ public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 4; /* 5 GHz band DFS channels */
+ /**
+ * 2.4Ghz band + DFS channels from 5 GHz band only
+ * @hide
+ */
+ public static final int WIFI_BAND_24_GHZ_WITH_5GHZ_DFS = 5;
/** 5 GHz band including DFS channels */
public static final int WIFI_BAND_5_GHZ_WITH_DFS = 6; /* 5 GHz band with DFS channels */
/** Both 2.4 GHz band and 5 GHz band; no DFS channels */
public static final int WIFI_BAND_BOTH = 3; /* both bands without DFS channels */
/** Both 2.4 GHz band and 5 GHz band; with DFS channels */
public static final int WIFI_BAND_BOTH_WITH_DFS = 7; /* both bands with DFS channels */
+ /**
+ * Max band value
+ * @hide
+ */
+ public static final int WIFI_BAND_MAX = 8;
/** Minimum supported scanning period */
public static final int MIN_SCAN_PERIOD_MS = 1000; /* minimum supported period */
@@ -375,19 +386,27 @@ public class WifiScanner {
*/
private int mBandScanned;
/** all scan results discovered in this scan, sorted by timestamp in ascending order */
- private ScanResult mResults[];
+ private final List<ScanResult> mResults;
- ScanData() {}
+ ScanData() {
+ mResults = new ArrayList<>();
+ }
public ScanData(int id, int flags, ScanResult[] results) {
mId = id;
mFlags = flags;
- mResults = results;
+ mResults = new ArrayList<>(Arrays.asList(results));
}
/** {@hide} */
public ScanData(int id, int flags, int bucketsScanned, int bandScanned,
ScanResult[] results) {
+ this(id, flags, bucketsScanned, bandScanned, new ArrayList<>(Arrays.asList(results)));
+ }
+
+ /** {@hide} */
+ public ScanData(int id, int flags, int bucketsScanned, int bandScanned,
+ List<ScanResult> results) {
mId = id;
mFlags = flags;
mBucketsScanned = bucketsScanned;
@@ -400,11 +419,9 @@ public class WifiScanner {
mFlags = s.mFlags;
mBucketsScanned = s.mBucketsScanned;
mBandScanned = s.mBandScanned;
- mResults = new ScanResult[s.mResults.length];
- for (int i = 0; i < s.mResults.length; i++) {
- ScanResult result = s.mResults[i];
- ScanResult newResult = new ScanResult(result);
- mResults[i] = newResult;
+ mResults = new ArrayList<>();
+ for (ScanResult scanResult : s.mResults) {
+ mResults.add(new ScanResult(scanResult));
}
}
@@ -427,7 +444,14 @@ public class WifiScanner {
}
public ScanResult[] getResults() {
- return mResults;
+ return mResults.toArray(new ScanResult[0]);
+ }
+
+ /** {@hide} */
+ public void addResults(@NonNull ScanResult[] newResults) {
+ for (ScanResult result : newResults) {
+ mResults.add(new ScanResult(result));
+ }
}
/** Implement the Parcelable interface {@hide} */
@@ -437,19 +461,11 @@ public class WifiScanner {
/** Implement the Parcelable interface {@hide} */
public void writeToParcel(Parcel dest, int flags) {
- if (mResults != null) {
- dest.writeInt(mId);
- dest.writeInt(mFlags);
- dest.writeInt(mBucketsScanned);
- dest.writeInt(mBandScanned);
- dest.writeInt(mResults.length);
- for (int i = 0; i < mResults.length; i++) {
- ScanResult result = mResults[i];
- result.writeToParcel(dest, flags);
- }
- } else {
- dest.writeInt(0);
- }
+ dest.writeInt(mId);
+ dest.writeInt(mFlags);
+ dest.writeInt(mBucketsScanned);
+ dest.writeInt(mBandScanned);
+ dest.writeParcelableList(mResults, 0);
}
/** Implement the Parcelable interface {@hide} */
@@ -460,11 +476,8 @@ public class WifiScanner {
int flags = in.readInt();
int bucketsScanned = in.readInt();
int bandScanned = in.readInt();
- int n = in.readInt();
- ScanResult results[] = new ScanResult[n];
- for (int i = 0; i < n; i++) {
- results[i] = ScanResult.CREATOR.createFromParcel(in);
- }
+ List<ScanResult> results = new ArrayList<>();
+ in.readParcelableList(results, ScanResult.class.getClassLoader());
return new ScanData(id, flags, bucketsScanned, bandScanned, results);
}
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index ba9fc786afe7..6d7e621a9bc2 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -19,7 +19,6 @@ package android.net.wifi;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import android.net.MacAddress;
@@ -62,7 +61,8 @@ public class WifiConfigurationTest {
config.updateIdentifier = "1234";
config.fromWifiNetworkSpecifier = true;
config.fromWifiNetworkSuggestion = true;
- MacAddress macBeforeParcel = config.getOrCreateRandomizedMacAddress();
+ config.setRandomizedMacAddress(MacAddress.createRandomUnicastAddress());
+ MacAddress macBeforeParcel = config.getRandomizedMacAddress();
Parcel parcelW = Parcel.obtain();
config.writeToParcel(parcelW, 0);
byte[] bytes = parcelW.marshall();
@@ -75,7 +75,7 @@ public class WifiConfigurationTest {
// lacking a useful config.equals, check two fields near the end.
assertEquals(cookie, reconfig.getMoTree());
- assertEquals(macBeforeParcel, reconfig.getOrCreateRandomizedMacAddress());
+ assertEquals(macBeforeParcel, reconfig.getRandomizedMacAddress());
assertEquals(config.updateIdentifier, reconfig.updateIdentifier);
assertFalse(reconfig.trusted);
assertTrue(config.fromWifiNetworkSpecifier);
@@ -193,19 +193,6 @@ public class WifiConfigurationTest {
}
@Test
- public void testGetOrCreateRandomizedMacAddress_SavesAndReturnsSameAddress() {
- WifiConfiguration config = new WifiConfiguration();
- MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
- assertEquals(defaultMac, config.getRandomizedMacAddress());
-
- MacAddress firstMacAddress = config.getOrCreateRandomizedMacAddress();
- MacAddress secondMacAddress = config.getOrCreateRandomizedMacAddress();
-
- assertNotEquals(defaultMac, firstMacAddress);
- assertEquals(firstMacAddress, secondMacAddress);
- }
-
- @Test
public void testSetRandomizedMacAddress_ChangesSavedAddress() {
WifiConfiguration config = new WifiConfiguration();
MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
@@ -219,36 +206,6 @@ public class WifiConfigurationTest {
}
@Test
- public void testGetOrCreateRandomizedMacAddress_ReRandomizesInvalidAddress() {
- WifiConfiguration config = new WifiConfiguration();
-
- MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
- MacAddress macAddressZeroes = MacAddress.ALL_ZEROS_ADDRESS;
- MacAddress macAddressMulticast = MacAddress.fromString("03:ff:ff:ff:ff:ff");
- MacAddress macAddressGlobal = MacAddress.fromString("fc:ff:ff:ff:ff:ff");
-
- config.setRandomizedMacAddress(null);
- MacAddress macAfterChange = config.getOrCreateRandomizedMacAddress();
- assertNotEquals(macAfterChange, null);
-
- config.setRandomizedMacAddress(defaultMac);
- macAfterChange = config.getOrCreateRandomizedMacAddress();
- assertNotEquals(macAfterChange, defaultMac);
-
- config.setRandomizedMacAddress(macAddressZeroes);
- macAfterChange = config.getOrCreateRandomizedMacAddress();
- assertNotEquals(macAfterChange, macAddressZeroes);
-
- config.setRandomizedMacAddress(macAddressMulticast);
- macAfterChange = config.getOrCreateRandomizedMacAddress();
- assertNotEquals(macAfterChange, macAddressMulticast);
-
- config.setRandomizedMacAddress(macAddressGlobal);
- macAfterChange = config.getOrCreateRandomizedMacAddress();
- assertNotEquals(macAfterChange, macAddressGlobal);
- }
-
- @Test
public void testSetRandomizedMacAddress_DoesNothingWhenNull() {
WifiConfiguration config = new WifiConfiguration();
MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index dd05b47fbd4f..ea136d62b202 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -22,7 +22,6 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -445,4 +444,37 @@ public class WifiScannerTest {
assertEquals(WifiScanner.CMD_STOP_PNO_SCAN, message.what);
}
+
+ @Test
+ public void testScanDataAddResults() throws Exception {
+ ScanResult scanResult1 = new ScanResult();
+ scanResult1.SSID = TEST_SSID_1;
+ ScanData scanData = new ScanData(0, 0, new ScanResult[]{scanResult1});
+
+ ScanResult scanResult2 = new ScanResult();
+ scanResult2.SSID = TEST_SSID_2;
+ scanData.addResults(new ScanResult[]{scanResult2});
+
+ ScanResult[] consolidatedScanResults = scanData.getResults();
+ assertEquals(2, consolidatedScanResults.length);
+ assertEquals(TEST_SSID_1, consolidatedScanResults[0].SSID);
+ assertEquals(TEST_SSID_2, consolidatedScanResults[1].SSID);
+ }
+
+ @Test
+ public void testScanDataParcel() throws Exception {
+ ScanResult scanResult1 = new ScanResult();
+ scanResult1.SSID = TEST_SSID_1;
+ ScanData scanData = new ScanData(5, 4, new ScanResult[]{scanResult1});
+
+ Parcel parcel = Parcel.obtain();
+ scanData.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ ScanData readScanData = ScanData.CREATOR.createFromParcel(parcel);
+
+ assertEquals(scanData.getId(), readScanData.getId());
+ assertEquals(scanData.getFlags(), readScanData.getFlags());
+ assertEquals(scanData.getResults().length, readScanData.getResults().length);
+ assertEquals(scanData.getResults()[0].SSID, readScanData.getResults()[0].SSID);
+ }
}