Merge "Import RotateSecondaryKeyTask"
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index c2bdb6c..2f5f555 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -1157,22 +1157,14 @@
private void removeAll(Predicate<JobStatus> predicate) {
for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
final ArraySet<JobStatus> jobs = mJobs.valueAt(jobSetIndex);
- for (int jobIndex = jobs.size() - 1; jobIndex >= 0; jobIndex--) {
- if (predicate.test(jobs.valueAt(jobIndex))) {
- jobs.removeAt(jobIndex);
- }
- }
+ jobs.removeIf(predicate);
if (jobs.size() == 0) {
mJobs.removeAt(jobSetIndex);
}
}
for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(jobSetIndex);
- for (int jobIndex = jobs.size() - 1; jobIndex >= 0; jobIndex--) {
- if (predicate.test(jobs.valueAt(jobIndex))) {
- jobs.removeAt(jobIndex);
- }
- }
+ jobs.removeIf(predicate);
if (jobs.size() == 0) {
mJobsPerSourceUid.removeAt(jobSetIndex);
}
diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp
new file mode 100644
index 0000000..786e8b0
--- /dev/null
+++ b/apex/statsd/service/Android.bp
@@ -0,0 +1,16 @@
+// Statsd Service jar, which will eventually be put in the statsd mainline apex.
+// statsd-service needs to be added to PRODUCT_SYSTEM_SERVER_JARS.
+// This jar will contain StatsCompanionService
+java_library {
+ name: "statsd-service",
+ installable: true,
+
+ srcs: [
+ "java/**/*.java",
+ ],
+
+ libs: [
+ "framework",
+ "services.core",
+ ],
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
similarity index 100%
rename from services/core/java/com/android/server/stats/StatsCompanionService.java
rename to apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
diff --git a/api/current.txt b/api/current.txt
index fc7685d..44cb997 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -44291,6 +44291,7 @@
field public static final String KEY_USE_HFA_FOR_PROVISIONING_BOOL = "use_hfa_for_provisioning_bool";
field public static final String KEY_USE_OTASP_FOR_PROVISIONING_BOOL = "use_otasp_for_provisioning_bool";
field public static final String KEY_USE_RCS_PRESENCE_BOOL = "use_rcs_presence_bool";
+ field public static final String KEY_USE_RCS_SIP_OPTIONS_BOOL = "use_rcs_sip_options_bool";
field public static final String KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL = "voicemail_notification_persistent_bool";
field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
diff --git a/api/system-current.txt b/api/system-current.txt
index 279d2c8..fcf69a7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -7858,6 +7858,8 @@
method public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onCallDisconnectCauseChanged(int, int);
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
+ method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
+ method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
method public void onRadioPowerStateChanged(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 9b00a42..ee631be 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2890,6 +2890,11 @@
method public static void setMinMatchForTest(int);
}
+ public class PhoneStateListener {
+ field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER = 268435456; // 0x10000000
+ field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER = 536870912; // 0x20000000
+ }
+
public class ServiceState implements android.os.Parcelable {
method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
method public void setCdmaSystemAndNetworkId(int, int);
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index e0f7d86..b37ee74 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -65,6 +65,14 @@
private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account";
private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer";
private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer";
+ /**
+ * Change the system dialer package name if a package name was specified,
+ * Example: adb shell telecom set-system-dialer <PACKAGE>
+ *
+ * Restore it to the default if if argument is "default" or no argument is passed.
+ * Example: adb shell telecom set-system-dialer default
+ */
+ private static final String COMMAND_SET_SYSTEM_DIALER = "set-system-dialer";
private static final String COMMAND_GET_SYSTEM_DIALER = "get-system-dialer";
private static final String COMMAND_WAIT_ON_HANDLERS = "wait-on-handlers";
private static final String COMMAND_SET_SIM_COUNT = "set-sim-count";
@@ -193,6 +201,9 @@
case COMMAND_GET_DEFAULT_DIALER:
runGetDefaultDialer();
break;
+ case COMMAND_SET_SYSTEM_DIALER:
+ runSetSystemDialer();
+ break;
case COMMAND_GET_SYSTEM_DIALER:
runGetSystemDialer();
break;
@@ -297,6 +308,12 @@
System.out.println("Success - " + packageName + " set as override default dialer.");
}
+ private void runSetSystemDialer() throws RemoteException {
+ final String packageName = nextArg();
+ mTelecomService.setSystemDialerPackage(packageName.equals("default") ? null : packageName);
+ System.out.println("Success - " + packageName + " set as override system dialer.");
+ }
+
private void runGetDefaultDialer() throws RemoteException {
System.out.println(mTelecomService.getDefaultDialerPackage());
}
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 8765760..b873be3 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -153,4 +153,11 @@
* Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead.
*/
protected abstract DevicePolicyCache getDevicePolicyCache();
+
+ /**
+ * @return cached version of device state related to DPM that can be accessed without risking
+ * deadlocks.
+ * Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead.
+ */
+ protected abstract DeviceStateCache getDeviceStateCache();
}
diff --git a/core/java/android/app/admin/DeviceStateCache.java b/core/java/android/app/admin/DeviceStateCache.java
new file mode 100644
index 0000000..7619aa2
--- /dev/null
+++ b/core/java/android/app/admin/DeviceStateCache.java
@@ -0,0 +1,56 @@
+/*
+ * 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.app.admin;
+
+import com.android.server.LocalServices;
+
+/**
+ * Stores a copy of the set of device state maintained by {@link DevicePolicyManager} which
+ * is not directly related to admin policies. This lives in its own class so that the state
+ * can be accessed from any place without risking dead locks.
+ *
+ * @hide
+ */
+public abstract class DeviceStateCache {
+ protected DeviceStateCache() {
+ }
+
+ /**
+ * @return the instance.
+ */
+ public static DeviceStateCache getInstance() {
+ final DevicePolicyManagerInternal dpmi =
+ LocalServices.getService(DevicePolicyManagerInternal.class);
+ return (dpmi != null) ? dpmi.getDeviceStateCache() : EmptyDeviceStateCache.INSTANCE;
+ }
+
+ /**
+ * See {@link DevicePolicyManager#isDeviceProvisioned}
+ */
+ public abstract boolean isDeviceProvisioned();
+
+ /**
+ * Empty implementation.
+ */
+ private static class EmptyDeviceStateCache extends DeviceStateCache {
+ private static final EmptyDeviceStateCache INSTANCE = new EmptyDeviceStateCache();
+
+ @Override
+ public boolean isDeviceProvisioned() {
+ return false;
+ }
+ }
+}
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index a5b71f6..59fb3d9 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -86,12 +86,22 @@
public abstract void setDeviceManaged(boolean isManaged);
/**
+ * Returns whether the device is managed by device owner.
+ */
+ public abstract boolean isDeviceManaged();
+
+ /**
* Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to update
* whether the user is managed by profile owner.
*/
public abstract void setUserManaged(int userId, boolean isManaged);
/**
+ * whether a profile owner manages this user.
+ */
+ public abstract boolean isUserManaged(int userId);
+
+ /**
* Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to omit
* restriction check, because DevicePolicyManager must always be able to set user icon
* regardless of any restriction.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index d5559aa..6639fbf 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -187,8 +187,7 @@
private static native void nativeSetInputWindowInfo(long transactionObj, long nativeObject,
InputWindowHandle handle);
- private static native void nativeTransferTouchFocus(long transactionObj, IBinder fromToken,
- IBinder toToken);
+
private static native boolean nativeGetProtectedContentSupport();
private static native void nativeSetMetadata(long transactionObj, long nativeObject, int key,
Parcel data);
@@ -2239,22 +2238,6 @@
}
/**
- * Transfers touch focus from one window to another. It is possible for multiple windows to
- * have touch focus if they support split touch dispatch
- * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
- * method only transfers touch focus of the specified window without affecting
- * other windows that may also have touch focus at the same time.
- * @param fromToken The token of a window that currently has touch focus.
- * @param toToken The token of the window that should receive touch focus in
- * place of the first.
- * @hide
- */
- public Transaction transferTouchFocus(IBinder fromToken, IBinder toToken) {
- nativeTransferTouchFocus(mNativeObject, fromToken, toToken);
- return this;
- }
-
- /**
* Waits until any changes to input windows have been sent from SurfaceFlinger to
* InputFlinger before returning.
*
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index 5ea970d..8283eb7 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -77,10 +77,10 @@
* @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)) {
+ debugLog(uid, changeId, state);
StatsLog.write(StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId,
state, mSource);
mReportedChanges.add(report);
@@ -89,7 +89,6 @@
}
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) {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index bf0f10e..1ca9383 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -458,15 +458,6 @@
transaction->setInputWindowInfo(ctrl, *handle->getInfo());
}
-static void nativeTransferTouchFocus(JNIEnv* env, jclass clazz, jlong transactionObj,
- jobject fromTokenObj, jobject toTokenObj) {
- auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
-
- sp<IBinder> fromToken(ibinderForJavaObject(env, fromTokenObj));
- sp<IBinder> toToken(ibinderForJavaObject(env, toTokenObj));
- transaction->transferTouchFocus(fromToken, toToken);
-}
-
static void nativeSyncInputWindows(JNIEnv* env, jclass clazz, jlong transactionObj) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
transaction->syncInputWindows();
@@ -1381,8 +1372,6 @@
(void*)nativeCaptureLayers },
{"nativeSetInputWindowInfo", "(JJLandroid/view/InputWindowHandle;)V",
(void*)nativeSetInputWindowInfo },
- {"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)V",
- (void*)nativeTransferTouchFocus },
{"nativeSetMetadata", "(JJILandroid/os/Parcel;)V",
(void*)nativeSetMetadata },
{"nativeGetDisplayedContentSamplingAttributes",
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 072beae..c692097 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -323,14 +323,16 @@
<font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
</family>
<family lang="und-Mymr" variant="elegant">
- <font weight="400" style="normal">NotoSansMyanmar-Regular-ZawDecode.ttf</font>
- <font weight="700" style="normal">NotoSansMyanmar-Bold-ZawDecode.ttf</font>
+ <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
<font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
<font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
</family>
<family lang="und-Mymr" variant="compact">
- <font weight="400" style="normal">NotoSansMyanmarUI-Regular-ZawDecode.ttf</font>
- <font weight="700" style="normal">NotoSansMyanmarUI-Bold-ZawDecode.ttf</font>
+ <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
</family>
<family lang="und-Thaa">
<font weight="400" style="normal">NotoSansThaana-Regular.ttf</font>
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index b017384..f839213e 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -43,41 +43,28 @@
if (!matrix.rectStaysRect()) return true;
SkRect dstDevRect = matrix.mapRect(dstRect);
float dstW, dstH;
- bool requiresIntegerTranslate = false;
if (MathUtils::isZero(matrix.getScaleX()) && MathUtils::isZero(matrix.getScaleY())) {
// Has a 90 or 270 degree rotation, although total matrix may also have scale factors
// in m10 and m01. Those scalings are automatically handled by mapRect so comparing
// dimensions is sufficient, but swap width and height comparison.
dstW = dstDevRect.height();
dstH = dstDevRect.width();
- requiresIntegerTranslate = true;
} else {
// Handle H/V flips or 180 rotation matrices. Axes may have been mirrored, but
// dimensions are still safe to compare directly.
dstW = dstDevRect.width();
dstH = dstDevRect.height();
- requiresIntegerTranslate =
- matrix.getScaleX() < -NON_ZERO_EPSILON || matrix.getScaleY() < -NON_ZERO_EPSILON;
}
if (!(MathUtils::areEqual(dstW, srcRect.width()) &&
MathUtils::areEqual(dstH, srcRect.height()))) {
return true;
}
- if (requiresIntegerTranslate) {
- // Device rect and source rect should be integer aligned to ensure there's no difference
- // in how nearest-neighbor sampling is resolved.
- return !(isIntegerAligned(srcRect.x()) &&
- isIntegerAligned(srcRect.y()) &&
- isIntegerAligned(dstDevRect.x()) &&
- isIntegerAligned(dstDevRect.y()));
- } else {
- // As long as src and device rects are translated by the same fractional amount,
- // filtering won't be needed
- return !(MathUtils::areEqual(SkScalarFraction(srcRect.x()),
- SkScalarFraction(dstDevRect.x())) &&
- MathUtils::areEqual(SkScalarFraction(srcRect.y()),
- SkScalarFraction(dstDevRect.y())));
- }
+ // Device rect and source rect should be integer aligned to ensure there's no difference
+ // in how nearest-neighbor sampling is resolved.
+ return !(isIntegerAligned(srcRect.x()) &&
+ isIntegerAligned(srcRect.y()) &&
+ isIntegerAligned(dstDevRect.x()) &&
+ isIntegerAligned(dstDevRect.y()));
}
bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer,
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 378064d..6257feb 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -34,6 +34,7 @@
"libutils",
"libbinder",
"libmedia",
+ "libmedia_codeclist",
"libmedia_jni_utils",
"libmedia_omx",
"libmediametrics",
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/FullBackupDataProcessor.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullBackupDataProcessor.java
new file mode 100644
index 0000000..f3ab2bd
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullBackupDataProcessor.java
@@ -0,0 +1,109 @@
+/*
+ * 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 android.app.backup.BackupTransport;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Accepts the full backup data stream and sends it to the server. */
+public interface FullBackupDataProcessor {
+ /**
+ * Prepares the upload.
+ *
+ * <p>After this, call {@link #start()} to establish the connection.
+ *
+ * @param inputStream to read the backup data from, calling {@link #finish} or {@link #cancel}
+ * will close the stream
+ * @return {@code true} if the connection was set up successfully, otherwise {@code false}
+ */
+ boolean initiate(InputStream inputStream) throws IOException;
+
+ /**
+ * Starts the upload, establishing the connection to the server.
+ *
+ * <p>After this, call {@link #pushData(int)} to request that the processor reads data from the
+ * socket, and uploads it to the server.
+ *
+ * <p>After this you must call one of {@link #cancel()}, {@link #finish()}, {@link
+ * #handleCheckSizeRejectionZeroBytes()}, {@link #handleCheckSizeRejectionQuotaExceeded()} or
+ * {@link #handleSendBytesQuotaExceeded()} to close the upload.
+ */
+ void start();
+
+ /**
+ * Requests that the processor read {@code numBytes} from the input stream passed in {@link
+ * #initiate(InputStream)} and upload them to the server.
+ *
+ * @return {@link BackupTransport#TRANSPORT_OK} if the upload succeeds, or {@link
+ * BackupTransport#TRANSPORT_QUOTA_EXCEEDED} if the upload exceeded the server-side app size
+ * quota, or {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} for other errors.
+ */
+ int pushData(int numBytes);
+
+ /** Cancels the upload and tears down the connection. */
+ void cancel();
+
+ /**
+ * Finish the upload and tear down the connection.
+ *
+ * <p>Call this after there is no more data to push with {@link #pushData(int)}.
+ *
+ * @return One of {@link BackupTransport#TRANSPORT_OK} if the app upload succeeds, {@link
+ * BackupTransport#TRANSPORT_QUOTA_EXCEEDED} if the upload exceeded the server-side app size
+ * quota, {@link BackupTransport#TRANSPORT_ERROR} for server 500s, or {@link
+ * BackupTransport#TRANSPORT_PACKAGE_REJECTED} for other errors.
+ */
+ int finish();
+
+ /**
+ * Notifies the processor that the current upload should be terminated because the estimated
+ * size is zero.
+ */
+ void handleCheckSizeRejectionZeroBytes();
+
+ /**
+ * Notifies the processor that the current upload should be terminated because the estimated
+ * size exceeds the quota.
+ */
+ void handleCheckSizeRejectionQuotaExceeded();
+
+ /**
+ * Notifies this class that the current upload should be terminated because the quota was
+ * exceeded during upload.
+ */
+ void handleSendBytesQuotaExceeded();
+
+ /**
+ * Attaches {@link FullBackupCallbacks} which the processor will notify when the backup
+ * succeeds.
+ */
+ void attachCallbacks(FullBackupCallbacks fullBackupCallbacks);
+
+ /**
+ * Implemented by the caller of the processor to receive notification of when the backup
+ * succeeds.
+ */
+ interface FullBackupCallbacks {
+ /** The processor calls this to indicate that the current backup has succeeded. */
+ void onSuccess();
+
+ /** The processor calls this if the upload failed for a non-transient reason. */
+ void onTransferFailed();
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDataProcessor.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDataProcessor.java
new file mode 100644
index 0000000..e4c4049
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDataProcessor.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+/**
+ * Retrieves the data during a full restore, decrypting it if necessary.
+ *
+ * <p>Use {@link FullRestoreDataProcessorFactory} to construct the encrypted or unencrypted
+ * processor as appropriate during restore.
+ */
+public interface FullRestoreDataProcessor {
+ /** Return value of {@link #readNextChunk} when there is no more data to download. */
+ int END_OF_STREAM = -1;
+
+ /**
+ * Reads the next chunk of restore data and writes it to the given buffer.
+ *
+ * <p>Where necessary, will open the connection to the server and/or decrypt the backup file.
+ *
+ * <p>The implementation may retry various errors. If the retries fail it will throw the
+ * relevant exception.
+ *
+ * @return the number of bytes read, or {@link #END_OF_STREAM} if there is no more data
+ * @throws IOException when downloading from the network or writing to disk
+ */
+ int readNextChunk(byte[] buffer) throws IOException;
+
+ /**
+ * Closes the connection to the server, deletes any temporary files and optionally sends a log
+ * with the given finish type.
+ *
+ * @param finishType one of {@link FullRestoreDownloader.FinishType}
+ */
+ void finish(FullRestoreDownloader.FinishType finishType);
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/StreamUtils.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/StreamUtils.java
new file mode 100644
index 0000000..91b2926
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/StreamUtils.java
@@ -0,0 +1,36 @@
+/*
+ * 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.Closeable;
+import java.io.IOException;
+
+/** Utility methods for dealing with Streams */
+public class StreamUtils {
+ /**
+ * Close a Closeable and silently ignore any IOExceptions.
+ *
+ * @param closeable The closeable to close
+ */
+ public static void closeQuietly(Closeable closeable) {
+ try {
+ closeable.close();
+ } catch (IOException ioe) {
+ // Silently ignore
+ }
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTask.java
new file mode 100644
index 0000000..a938d71
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTask.java
@@ -0,0 +1,197 @@
+/*
+ * 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.content.Context;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.backup.encryption.StreamUtils;
+import com.android.server.backup.encryption.chunking.ProtoStore;
+import com.android.server.backup.encryption.chunking.cdc.FingerprintMixer;
+import com.android.server.backup.encryption.client.CryptoBackupServer;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.TertiaryKeyManager;
+import com.android.server.backup.encryption.keys.TertiaryKeyRotationScheduler;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkListing;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.concurrent.Callable;
+
+import javax.crypto.SecretKey;
+
+/**
+ * Task which reads a stream of plaintext full backup data, chunks it, encrypts it and uploads it to
+ * the server.
+ *
+ * <p>Once the backup completes or fails, closes the input stream.
+ */
+public class EncryptedFullBackupTask implements Callable<Void> {
+ private static final String TAG = "EncryptedFullBackupTask";
+
+ private static final int MIN_CHUNK_SIZE_BYTES = 2 * 1024;
+ private static final int MAX_CHUNK_SIZE_BYTES = 64 * 1024;
+ private static final int AVERAGE_CHUNK_SIZE_BYTES = 4 * 1024;
+
+ // TODO(b/69350270): Remove this hard-coded salt and related logic once we feel confident that
+ // incremental backup has happened at least once for all existing packages/users since we moved
+ // to
+ // using a randomly generated salt.
+ //
+ // The hard-coded fingerprint mixer salt was used for a short time period before replaced by one
+ // that is randomly generated on initial non-incremental backup and stored in ChunkListing to be
+ // reused for succeeding incremental backups. If an old ChunkListing does not have a
+ // fingerprint_mixer_salt, we assume that it was last backed up before a randomly generated salt
+ // is used so we use the hardcoded salt and set ChunkListing#fingerprint_mixer_salt to this
+ // value.
+ // Eventually all backup ChunkListings will have this field set and then we can remove the
+ // default
+ // value in the code.
+ static final byte[] DEFAULT_FINGERPRINT_MIXER_SALT =
+ Arrays.copyOf(new byte[] {20, 23}, FingerprintMixer.SALT_LENGTH_BYTES);
+
+ private final ProtoStore<ChunkListing> mChunkListingStore;
+ private final TertiaryKeyManager mTertiaryKeyManager;
+ private final InputStream mInputStream;
+ private final EncryptedBackupTask mTask;
+ private final String mPackageName;
+ private final SecureRandom mSecureRandom;
+
+ /** Creates a new instance with the default min, max and average chunk sizes. */
+ public static EncryptedFullBackupTask newInstance(
+ Context context,
+ CryptoBackupServer cryptoBackupServer,
+ SecureRandom secureRandom,
+ RecoverableKeyStoreSecondaryKey secondaryKey,
+ String packageName,
+ InputStream inputStream)
+ throws IOException {
+ EncryptedBackupTask encryptedBackupTask =
+ new EncryptedBackupTask(
+ cryptoBackupServer,
+ secureRandom,
+ packageName,
+ new BackupStreamEncrypter(
+ inputStream,
+ MIN_CHUNK_SIZE_BYTES,
+ MAX_CHUNK_SIZE_BYTES,
+ AVERAGE_CHUNK_SIZE_BYTES));
+ TertiaryKeyManager tertiaryKeyManager =
+ new TertiaryKeyManager(
+ context,
+ secureRandom,
+ TertiaryKeyRotationScheduler.getInstance(context),
+ secondaryKey,
+ packageName);
+
+ return new EncryptedFullBackupTask(
+ ProtoStore.createChunkListingStore(context),
+ tertiaryKeyManager,
+ encryptedBackupTask,
+ inputStream,
+ packageName,
+ new SecureRandom());
+ }
+
+ @VisibleForTesting
+ EncryptedFullBackupTask(
+ ProtoStore<ChunkListing> chunkListingStore,
+ TertiaryKeyManager tertiaryKeyManager,
+ EncryptedBackupTask task,
+ InputStream inputStream,
+ String packageName,
+ SecureRandom secureRandom) {
+ mChunkListingStore = chunkListingStore;
+ mTertiaryKeyManager = tertiaryKeyManager;
+ mInputStream = inputStream;
+ mTask = task;
+ mPackageName = packageName;
+ mSecureRandom = secureRandom;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ try {
+ Optional<ChunkListing> maybeOldChunkListing =
+ mChunkListingStore.loadProto(mPackageName);
+
+ if (maybeOldChunkListing.isPresent()) {
+ Slog.i(TAG, "Found previous chunk listing for " + mPackageName);
+ }
+
+ // If the key has been rotated then we must re-encrypt all of the backup data.
+ if (mTertiaryKeyManager.wasKeyRotated()) {
+ Slog.i(
+ TAG,
+ "Key was rotated or newly generated for "
+ + mPackageName
+ + ", so performing a full backup.");
+ maybeOldChunkListing = Optional.empty();
+ mChunkListingStore.deleteProto(mPackageName);
+ }
+
+ SecretKey tertiaryKey = mTertiaryKeyManager.getKey();
+ WrappedKeyProto.WrappedKey wrappedTertiaryKey = mTertiaryKeyManager.getWrappedKey();
+
+ ChunkListing newChunkListing;
+ if (!maybeOldChunkListing.isPresent()) {
+ byte[] fingerprintMixerSalt = new byte[FingerprintMixer.SALT_LENGTH_BYTES];
+ mSecureRandom.nextBytes(fingerprintMixerSalt);
+ newChunkListing =
+ mTask.performNonIncrementalBackup(
+ tertiaryKey, wrappedTertiaryKey, fingerprintMixerSalt);
+ } else {
+ ChunkListing oldChunkListing = maybeOldChunkListing.get();
+
+ if (oldChunkListing.fingerprintMixerSalt == null
+ || oldChunkListing.fingerprintMixerSalt.length == 0) {
+ oldChunkListing.fingerprintMixerSalt = DEFAULT_FINGERPRINT_MIXER_SALT;
+ }
+
+ newChunkListing =
+ mTask.performIncrementalBackup(
+ tertiaryKey, wrappedTertiaryKey, oldChunkListing);
+ }
+
+ mChunkListingStore.saveProto(mPackageName, newChunkListing);
+ Slog.v(TAG, "Saved chunk listing for " + mPackageName);
+ } catch (IOException e) {
+ Slog.e(TAG, "Storage exception, wiping state");
+ mChunkListingStore.deleteProto(mPackageName);
+ throw e;
+ } finally {
+ StreamUtils.closeQuietly(mInputStream);
+ }
+
+ return null;
+ }
+
+ /**
+ * Signals to the task that the backup has been cancelled. If the upload has not yet started
+ * then the task will not upload any data to the server or save the new chunk listing.
+ *
+ * <p>You must then terminate the input stream.
+ */
+ public void cancel() {
+ mTask.cancel();
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTask.java
new file mode 100644
index 0000000..04381af
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTask.java
@@ -0,0 +1,137 @@
+/*
+ * 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 android.annotation.Nullable;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.backup.encryption.FullRestoreDataProcessor;
+import com.android.server.backup.encryption.FullRestoreDownloader;
+import com.android.server.backup.encryption.StreamUtils;
+import com.android.server.backup.encryption.chunking.DecryptedChunkFileOutput;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+
+/** Downloads the encrypted backup file, decrypts it and passes the data to backup manager. */
+public class EncryptedFullRestoreTask implements FullRestoreDataProcessor {
+ private static final String DEFAULT_TEMPORARY_FOLDER = "encrypted_restore_temp";
+ private static final String ENCRYPTED_FILE_NAME = "encrypted_restore";
+ private static final String DECRYPTED_FILE_NAME = "decrypted_restore";
+
+ private final FullRestoreToFileTask mFullRestoreToFileTask;
+ private final BackupFileDecryptorTask mBackupFileDecryptorTask;
+ private final File mEncryptedFile;
+ private final File mDecryptedFile;
+ @Nullable private InputStream mDecryptedFileInputStream;
+
+ /**
+ * Creates a new task which stores temporary files in the files directory.
+ *
+ * @param fullRestoreDownloader which will download the backup file
+ * @param tertiaryKey which the backup file is encrypted with
+ */
+ public static EncryptedFullRestoreTask newInstance(
+ Context context, FullRestoreDownloader fullRestoreDownloader, SecretKey tertiaryKey)
+ throws NoSuchAlgorithmException, NoSuchPaddingException {
+ File temporaryFolder = new File(context.getFilesDir(), DEFAULT_TEMPORARY_FOLDER);
+ temporaryFolder.mkdirs();
+ return new EncryptedFullRestoreTask(
+ temporaryFolder, fullRestoreDownloader, new BackupFileDecryptorTask(tertiaryKey));
+ }
+
+ @VisibleForTesting
+ EncryptedFullRestoreTask(
+ File temporaryFolder,
+ FullRestoreDownloader fullRestoreDownloader,
+ BackupFileDecryptorTask backupFileDecryptorTask) {
+ checkArgument(temporaryFolder.isDirectory(), "Temporary folder must be existing directory");
+
+ mEncryptedFile = new File(temporaryFolder, ENCRYPTED_FILE_NAME);
+ mDecryptedFile = new File(temporaryFolder, DECRYPTED_FILE_NAME);
+
+ mFullRestoreToFileTask = new FullRestoreToFileTask(fullRestoreDownloader);
+ mBackupFileDecryptorTask = backupFileDecryptorTask;
+ }
+
+ /**
+ * Reads the next decrypted bytes into the given buffer.
+ *
+ * <p>During the first call this method will download the backup file from the server, decrypt
+ * it and save it to disk. It will then read the bytes from the file on disk.
+ *
+ * <p>Once this method has read all the bytes of the file, the caller must call {@link #finish}
+ * to clean up.
+ *
+ * @return the number of bytes read, or {@code -1} on reaching the end of the file
+ */
+ @Override
+ public int readNextChunk(byte[] buffer) throws IOException {
+ if (mDecryptedFileInputStream == null) {
+ try {
+ mDecryptedFileInputStream = downloadAndDecryptBackup();
+ } catch (BadPaddingException
+ | InvalidKeyException
+ | NoSuchAlgorithmException
+ | IllegalBlockSizeException
+ | ShortBufferException
+ | EncryptedRestoreException
+ | InvalidAlgorithmParameterException e) {
+ throw new IOException("Encryption issue", e);
+ }
+ }
+
+ return mDecryptedFileInputStream.read(buffer);
+ }
+
+ private InputStream downloadAndDecryptBackup()
+ throws IOException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException,
+ IllegalBlockSizeException, ShortBufferException, EncryptedRestoreException,
+ InvalidAlgorithmParameterException {
+ mFullRestoreToFileTask.restoreToFile(mEncryptedFile);
+ mBackupFileDecryptorTask.decryptFile(
+ mEncryptedFile, new DecryptedChunkFileOutput(mDecryptedFile));
+ mEncryptedFile.delete();
+ return new BufferedInputStream(new FileInputStream(mDecryptedFile));
+ }
+
+ /** Cleans up temporary files. */
+ @Override
+ public void finish(FullRestoreDownloader.FinishType unusedFinishType) {
+ // The download is finished and log sent during RestoreToFileTask#restoreToFile(), so we
+ // don't need to do either of those things here.
+
+ StreamUtils.closeQuietly(mDecryptedFileInputStream);
+ mEncryptedFile.delete();
+ mDecryptedFile.delete();
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/SizeQuotaExceededException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/SizeQuotaExceededException.java
new file mode 100644
index 0000000..515db86
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/SizeQuotaExceededException.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 aa backup has exceeded the space allowed for that user */
+public class SizeQuotaExceededException extends RuntimeException {
+ public SizeQuotaExceededException() {
+ super("Backup size quota exceeded.");
+ }
+}
diff --git a/packages/BackupEncryption/test/robolectric-integration/Android.bp b/packages/BackupEncryption/test/robolectric-integration/Android.bp
new file mode 100644
index 0000000..f696278
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric-integration/Android.bp
@@ -0,0 +1,33 @@
+// 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.
+
+android_robolectric_test {
+ name: "BackupEncryptionRoboIntegTests",
+ srcs: [
+ "src/**/*.java",
+ ],
+ java_resource_dirs: ["config"],
+ libs: [
+ "backup-encryption-protos",
+ "platform-test-annotations",
+ "testng",
+ "truth-prebuilt",
+ ],
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ ],
+ instrumentation_for: "BackupEncryption",
+}
diff --git a/packages/BackupEncryption/test/robolectric-integration/AndroidManifest.xml b/packages/BackupEncryption/test/robolectric-integration/AndroidManifest.xml
new file mode 100644
index 0000000..c3930cc
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric-integration/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ coreApp="true"
+ package="com.android.server.backup.encryption.robointeg">
+
+ <application/>
+
+</manifest>
diff --git a/packages/BackupEncryption/test/robolectric-integration/config/robolectric.properties b/packages/BackupEncryption/test/robolectric-integration/config/robolectric.properties
new file mode 100644
index 0000000..26fceb3
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric-integration/config/robolectric.properties
@@ -0,0 +1,17 @@
+#
+# 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.
+#
+
+sdk=NEWEST_SDK
diff --git a/packages/BackupEncryption/test/robolectric-integration/src/com/android/server/backup/encryption/RoundTripTest.java b/packages/BackupEncryption/test/robolectric-integration/src/com/android/server/backup/encryption/RoundTripTest.java
new file mode 100644
index 0000000..8ec68fd
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric-integration/src/com/android/server/backup/encryption/RoundTripTest.java
@@ -0,0 +1,217 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.backup.encryption.client.CryptoBackupServer;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.TertiaryKeyManager;
+import com.android.server.backup.encryption.keys.TertiaryKeyRotationScheduler;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+import com.android.server.backup.encryption.tasks.EncryptedFullBackupTask;
+import com.android.server.backup.encryption.tasks.EncryptedFullRestoreTask;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Map;
+
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+@RunWith(RobolectricTestRunner.class)
+public class RoundTripTest {
+ /** Amount of data we want to round trip in this test */
+ private static final int TEST_DATA_SIZE = 1024 * 1024; // 1MB
+
+ /** Buffer size used when reading data from the restore task */
+ private static final int READ_BUFFER_SIZE = 1024; // 1024 byte buffer.
+
+ /** Key parameters used for the secondary encryption key */
+ private static final String KEY_ALGORITHM = "AES";
+ private static final int KEY_SIZE_BITS = 256;
+
+ /** Package name for our test package */
+ private static final String TEST_PACKAGE_NAME = "com.android.backup.test";
+
+ /** The name we use to refer to our secondary key */
+ private static final String TEST_KEY_ALIAS = "test/backup/KEY_ALIAS";
+
+ /** Original data used for comparison after round trip */
+ private final byte[] mOriginalData = new byte[TEST_DATA_SIZE];
+
+ /** App context, used to store the key data and chunk listings */
+ private Context mContext;
+
+ /** The secondary key we're using for the test */
+ private RecoverableKeyStoreSecondaryKey mSecondaryKey;
+
+ /** Source of random material which is considered non-predictable in its' generation */
+ private SecureRandom mSecureRandom = new SecureRandom();
+
+ @Before
+ public void setUp() throws NoSuchAlgorithmException {
+ mContext = ApplicationProvider.getApplicationContext();
+ mSecondaryKey = new RecoverableKeyStoreSecondaryKey(TEST_KEY_ALIAS, generateAesKey());
+ fillBuffer(mOriginalData);
+ }
+
+ @Test
+ public void testRoundTrip() throws Exception {
+ byte[] backupData = performBackup(mOriginalData);
+ assertThat(backupData).isNotEqualTo(mOriginalData);
+ byte[] restoredData = performRestore(backupData);
+ assertThat(restoredData).isEqualTo(mOriginalData);
+ }
+
+ /** Perform a backup and return the backed-up representation of the data */
+ private byte[] performBackup(byte[] backupData) throws Exception {
+ DummyServer dummyServer = new DummyServer();
+ EncryptedFullBackupTask backupTask =
+ EncryptedFullBackupTask.newInstance(
+ mContext,
+ dummyServer,
+ mSecureRandom,
+ mSecondaryKey,
+ TEST_PACKAGE_NAME,
+ new ByteArrayInputStream(backupData));
+ backupTask.call();
+ return dummyServer.mStoredData;
+ }
+
+ /** Perform a restore and resturn the bytes obtained from the restore process */
+ private byte[] performRestore(byte[] backupData)
+ throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
+ InvalidAlgorithmParameterException, InvalidKeyException,
+ IllegalBlockSizeException {
+ ByteArrayOutputStream decryptedOutput = new ByteArrayOutputStream();
+
+ EncryptedFullRestoreTask restoreTask =
+ EncryptedFullRestoreTask.newInstance(
+ mContext, new FakeFullRestoreDownloader(backupData), getTertiaryKey());
+
+ byte[] buffer = new byte[READ_BUFFER_SIZE];
+ int bytesRead = restoreTask.readNextChunk(buffer);
+ while (bytesRead != -1) {
+ decryptedOutput.write(buffer, 0, bytesRead);
+ bytesRead = restoreTask.readNextChunk(buffer);
+ }
+
+ return decryptedOutput.toByteArray();
+ }
+
+ /** Get the tertiary key for our test package from the key manager */
+ private SecretKey getTertiaryKey()
+ throws IllegalBlockSizeException, InvalidAlgorithmParameterException,
+ NoSuchAlgorithmException, IOException, NoSuchPaddingException,
+ InvalidKeyException {
+ TertiaryKeyManager tertiaryKeyManager =
+ new TertiaryKeyManager(
+ mContext,
+ mSecureRandom,
+ TertiaryKeyRotationScheduler.getInstance(mContext),
+ mSecondaryKey,
+ TEST_PACKAGE_NAME);
+ return tertiaryKeyManager.getKey();
+ }
+
+ /** Fill a buffer with data in a predictable way */
+ private void fillBuffer(byte[] buffer) {
+ byte loopingCounter = 0;
+ for (int i = 0; i < buffer.length; i++) {
+ buffer[i] = loopingCounter;
+ loopingCounter++;
+ }
+ }
+
+ /** Generate a new, random, AES key */
+ public static SecretKey generateAesKey() throws NoSuchAlgorithmException {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
+ keyGenerator.init(KEY_SIZE_BITS);
+ return keyGenerator.generateKey();
+ }
+
+ /**
+ * Dummy backup data endpoint. This stores the data so we can use it
+ * in subsequent test steps.
+ */
+ private static class DummyServer implements CryptoBackupServer {
+ private static final String DUMMY_DOC_ID = "DummyDoc";
+
+ byte[] mStoredData = null;
+
+ @Override
+ public String uploadIncrementalBackup(
+ String packageName,
+ String oldDocId,
+ byte[] diffScript,
+ WrappedKeyProto.WrappedKey tertiaryKey) {
+ throw new RuntimeException("Not Implemented");
+ }
+
+ @Override
+ public String uploadNonIncrementalBackup(
+ String packageName, byte[] data, WrappedKeyProto.WrappedKey tertiaryKey) {
+ assertThat(packageName).isEqualTo(TEST_PACKAGE_NAME);
+ mStoredData = data;
+ return DUMMY_DOC_ID;
+ }
+
+ @Override
+ public void setActiveSecondaryKeyAlias(
+ String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys) {
+ throw new RuntimeException("Not Implemented");
+ }
+ }
+
+ /** 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();
+ 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/backup/encryption/tasks/EncryptedFullBackupTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java
new file mode 100644
index 0000000..096b2da
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.ProtoStore;
+import com.android.server.backup.encryption.chunking.cdc.FingerprintMixer;
+import com.android.server.backup.encryption.keys.TertiaryKeyManager;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkListing;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto.WrappedKey;
+import com.android.server.backup.testing.CryptoTestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Optional;
+
+import javax.crypto.SecretKey;
+
+@Config(shadows = {EncryptedBackupTaskTest.ShadowBackupFileBuilder.class})
+@RunWith(RobolectricTestRunner.class)
+public class EncryptedFullBackupTaskTest {
+ private static final String TEST_PACKAGE_NAME = "com.example.package";
+ private static final byte[] TEST_EXISTING_FINGERPRINT_MIXER_SALT =
+ Arrays.copyOf(new byte[] {11}, ChunkHash.HASH_LENGTH_BYTES);
+ private static final byte[] TEST_GENERATED_FINGERPRINT_MIXER_SALT =
+ Arrays.copyOf(new byte[] {22}, ChunkHash.HASH_LENGTH_BYTES);
+ private static final ChunkHash TEST_CHUNK_HASH_1 =
+ new ChunkHash(Arrays.copyOf(new byte[] {1}, ChunkHash.HASH_LENGTH_BYTES));
+ private static final ChunkHash TEST_CHUNK_HASH_2 =
+ new ChunkHash(Arrays.copyOf(new byte[] {2}, ChunkHash.HASH_LENGTH_BYTES));
+ private static final int TEST_CHUNK_LENGTH_1 = 20;
+ private static final int TEST_CHUNK_LENGTH_2 = 40;
+
+ @Mock private ProtoStore<ChunkListing> mChunkListingStore;
+ @Mock private TertiaryKeyManager mTertiaryKeyManager;
+ @Mock private InputStream mInputStream;
+ @Mock private EncryptedBackupTask mEncryptedBackupTask;
+ @Mock private SecretKey mTertiaryKey;
+ @Mock private SecureRandom mSecureRandom;
+
+ private EncryptedFullBackupTask mTask;
+ private ChunkListing mOldChunkListing;
+ private ChunkListing mNewChunkListing;
+ private WrappedKey mWrappedTertiaryKey;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mWrappedTertiaryKey = new WrappedKey();
+ when(mTertiaryKeyManager.getKey()).thenReturn(mTertiaryKey);
+ when(mTertiaryKeyManager.getWrappedKey()).thenReturn(mWrappedTertiaryKey);
+
+ mOldChunkListing =
+ CryptoTestUtils.newChunkListing(
+ /* docId */ null,
+ TEST_EXISTING_FINGERPRINT_MIXER_SALT,
+ ChunksMetadataProto.AES_256_GCM,
+ ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED,
+ CryptoTestUtils.newChunk(TEST_CHUNK_HASH_1.getHash(), TEST_CHUNK_LENGTH_1));
+ mNewChunkListing =
+ CryptoTestUtils.newChunkListing(
+ /* docId */ null,
+ /* fingerprintSalt */ null,
+ ChunksMetadataProto.AES_256_GCM,
+ ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED,
+ CryptoTestUtils.newChunk(TEST_CHUNK_HASH_1.getHash(), TEST_CHUNK_LENGTH_1),
+ CryptoTestUtils.newChunk(TEST_CHUNK_HASH_2.getHash(), TEST_CHUNK_LENGTH_2));
+ when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any()))
+ .thenReturn(mNewChunkListing);
+ when(mEncryptedBackupTask.performIncrementalBackup(any(), any(), any()))
+ .thenReturn(mNewChunkListing);
+ when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty());
+
+ doAnswer(invocation -> {
+ byte[] byteArray = (byte[]) invocation.getArguments()[0];
+ System.arraycopy(
+ TEST_GENERATED_FINGERPRINT_MIXER_SALT,
+ /* srcPos */ 0,
+ byteArray,
+ /* destPos */ 0,
+ FingerprintMixer.SALT_LENGTH_BYTES);
+ return null;
+ })
+ .when(mSecureRandom)
+ .nextBytes(any(byte[].class));
+
+ mTask =
+ new EncryptedFullBackupTask(
+ mChunkListingStore,
+ mTertiaryKeyManager,
+ mEncryptedBackupTask,
+ mInputStream,
+ TEST_PACKAGE_NAME,
+ mSecureRandom);
+ }
+
+ @Test
+ public void call_existingChunkListingButTertiaryKeyRotated_performsNonIncrementalBackup()
+ throws Exception {
+ when(mTertiaryKeyManager.wasKeyRotated()).thenReturn(true);
+ when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
+ .thenReturn(Optional.of(mOldChunkListing));
+
+ mTask.call();
+
+ verify(mEncryptedBackupTask)
+ .performNonIncrementalBackup(
+ eq(mTertiaryKey),
+ eq(mWrappedTertiaryKey),
+ eq(TEST_GENERATED_FINGERPRINT_MIXER_SALT));
+ }
+
+ @Test
+ public void call_noExistingChunkListing_performsNonIncrementalBackup() throws Exception {
+ when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty());
+ mTask.call();
+ verify(mEncryptedBackupTask)
+ .performNonIncrementalBackup(
+ eq(mTertiaryKey),
+ eq(mWrappedTertiaryKey),
+ eq(TEST_GENERATED_FINGERPRINT_MIXER_SALT));
+ }
+
+ @Test
+ public void call_existingChunkListing_performsIncrementalBackup() throws Exception {
+ when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
+ .thenReturn(Optional.of(mOldChunkListing));
+ mTask.call();
+ verify(mEncryptedBackupTask)
+ .performIncrementalBackup(
+ eq(mTertiaryKey), eq(mWrappedTertiaryKey), eq(mOldChunkListing));
+ }
+
+ @Test
+ public void
+ call_existingChunkListingWithNoFingerprintMixerSalt_doesntSetSaltBeforeIncBackup()
+ throws Exception {
+ mOldChunkListing.fingerprintMixerSalt = new byte[0];
+ when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
+ .thenReturn(Optional.of(mOldChunkListing));
+
+ mTask.call();
+
+ verify(mEncryptedBackupTask)
+ .performIncrementalBackup(
+ eq(mTertiaryKey), eq(mWrappedTertiaryKey), eq(mOldChunkListing));
+ }
+
+ @Test
+ public void call_noExistingChunkListing_storesNewChunkListing() throws Exception {
+ when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty());
+ mTask.call();
+ verify(mChunkListingStore).saveProto(TEST_PACKAGE_NAME, mNewChunkListing);
+ }
+
+ @Test
+ public void call_existingChunkListing_storesNewChunkListing() throws Exception {
+ when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
+ .thenReturn(Optional.of(mOldChunkListing));
+ mTask.call();
+ verify(mChunkListingStore).saveProto(TEST_PACKAGE_NAME, mNewChunkListing);
+ }
+
+ @Test
+ public void call_exceptionDuringBackup_doesNotSaveNewChunkListing() throws Exception {
+ when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty());
+ when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any()))
+ .thenThrow(GeneralSecurityException.class);
+
+ assertThrows(Exception.class, () -> mTask.call());
+
+ assertThat(mChunkListingStore.loadProto(TEST_PACKAGE_NAME).isPresent()).isFalse();
+ }
+
+ @Test
+ public void call_incrementalThrowsPermanentException_clearsState() throws Exception {
+ when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
+ .thenReturn(Optional.of(mOldChunkListing));
+ when(mEncryptedBackupTask.performIncrementalBackup(any(), any(), any()))
+ .thenThrow(IOException.class);
+
+ assertThrows(IOException.class, () -> mTask.call());
+
+ verify(mChunkListingStore).deleteProto(TEST_PACKAGE_NAME);
+ }
+
+ @Test
+ public void call_closesInputStream() throws Exception {
+ mTask.call();
+ verify(mInputStream).close();
+ }
+
+ @Test
+ public void cancel_cancelsTask() throws Exception {
+ mTask.cancel();
+ verify(mEncryptedBackupTask).cancel();
+ }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTaskTest.java
new file mode 100644
index 0000000..0affacd
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTaskTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.doAnswer;
+
+import static java.util.stream.Collectors.toList;
+
+import com.android.server.backup.encryption.FullRestoreDownloader;
+
+import com.google.common.io.Files;
+import com.google.common.primitives.Bytes;
+
+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.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+@RunWith(RobolectricTestRunner.class)
+public class EncryptedFullRestoreTaskTest {
+ private static final int TEST_BUFFER_SIZE = 10;
+ private static final byte[] TEST_ENCRYPTED_DATA = {1, 2, 3, 4, 5, 6};
+ private static final byte[] TEST_DECRYPTED_DATA = fakeDecrypt(TEST_ENCRYPTED_DATA);
+
+ @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Mock private BackupFileDecryptorTask mDecryptorTask;
+
+ private File mFolder;
+ private FakeFullRestoreDownloader mFullRestorePackageWrapper;
+ private EncryptedFullRestoreTask mTask;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mFolder = temporaryFolder.newFolder();
+ mFullRestorePackageWrapper = new FakeFullRestoreDownloader(TEST_ENCRYPTED_DATA);
+
+ doAnswer(
+ invocation -> {
+ File source = invocation.getArgument(0);
+ DecryptedChunkOutput target = invocation.getArgument(1);
+ byte[] decrypted = fakeDecrypt(Files.toByteArray(source));
+ target.open();
+ target.processChunk(decrypted, decrypted.length);
+ target.close();
+ return null;
+ })
+ .when(mDecryptorTask)
+ .decryptFile(any(), any());
+
+ mTask = new EncryptedFullRestoreTask(mFolder, mFullRestorePackageWrapper, mDecryptorTask);
+ }
+
+ @Test
+ public void readNextChunk_downloadsAndDecryptsBackup() throws Exception {
+ ByteArrayOutputStream decryptedOutput = new ByteArrayOutputStream();
+
+ byte[] buffer = new byte[TEST_BUFFER_SIZE];
+ int bytesRead = mTask.readNextChunk(buffer);
+ while (bytesRead != -1) {
+ decryptedOutput.write(buffer, 0, bytesRead);
+ bytesRead = mTask.readNextChunk(buffer);
+ }
+
+ assertThat(decryptedOutput.toByteArray()).isEqualTo(TEST_DECRYPTED_DATA);
+ }
+
+ @Test
+ public void finish_deletesTemporaryFiles() throws Exception {
+ mTask.readNextChunk(new byte[10]);
+ mTask.finish(FullRestoreDownloader.FinishType.UNKNOWN_FINISH);
+
+ assertThat(mFolder.listFiles()).isEmpty();
+ }
+
+ /** 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();
+ mData = new ByteArrayInputStream(data);
+ }
+
+ @Override
+ public int readNextChunk(byte[] buffer) throws IOException {
+ return mData.read(buffer);
+ }
+
+ @Override
+ public void finish(FinishType finishType) {
+ // Nothing to do.
+ }
+ }
+
+ /** Fake decrypts a byte array by subtracting 1 from each byte. */
+ private static byte[] fakeDecrypt(byte[] input) {
+ return Bytes.toArray(Bytes.asList(input).stream().map(b -> b + 1).collect(toList()));
+ }
+}
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 5cff53f..5dfd5ee 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
@@ -78,7 +78,10 @@
int orderingType,
ChunksMetadataProto.Chunk... chunks) {
ChunksMetadataProto.ChunkListing chunkListing = new ChunksMetadataProto.ChunkListing();
- chunkListing.fingerprintMixerSalt = Arrays.copyOf(fingerprintSalt, fingerprintSalt.length);
+ chunkListing.fingerprintMixerSalt =
+ fingerprintSalt == null
+ ? null
+ : Arrays.copyOf(fingerprintSalt, fingerprintSalt.length);
chunkListing.cipherType = cipherType;
chunkListing.chunkOrderingType = orderingType;
chunkListing.chunks = chunks;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
index afd722b..447e579 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
@@ -22,6 +22,7 @@
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.BatteryController;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -34,8 +35,9 @@
@Inject
public CarNotificationInterruptionStateProvider(Context context,
NotificationFilter filter,
- StatusBarStateController stateController) {
- super(context, filter, stateController);
+ StatusBarStateController stateController,
+ BatteryController batteryController) {
+ super(context, filter, stateController, batteryController);
}
@Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
index 58f80a4..d79849c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
@@ -257,6 +257,11 @@
return false;
}
+ @Override
+ public boolean isAodPowerSave() {
+ return false;
+ }
+
private void notifyBatteryLevelChanged() {
for (int i = 0, size = mChangeCallbacks.size(); i < size; i++) {
mChangeCallbacks.get(i)
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index c1cc261..e11c063 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -29,6 +29,7 @@
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_PRECISE_PHONE_STATE" />
+ <uses-permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 720074b..47d911d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2752,7 +2752,7 @@
private void checkIsHandlerThread() {
if (!mHandler.getLooper().isCurrentThread()) {
- Log.wtf(TAG, "must call on mHandler's thread "
+ Log.wtfStack(TAG, "must call on mHandler's thread "
+ mHandler.getLooper().getThread() + ", not " + Thread.currentThread());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
index 0fa80ac..d9b4297 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
@@ -18,6 +18,8 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.power.PowerUI;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsModule;
import dagger.Binds;
import dagger.Module;
@@ -27,7 +29,7 @@
/**
* SystemUI objects that are injectable should go here.
*/
-@Module
+@Module(includes = {RecentsModule.class})
public abstract class SystemUIBinder {
/** Inject into KeyguardViewMediator. */
@Binds
@@ -40,4 +42,10 @@
@IntoMap
@ClassKey(PowerUI.class)
public abstract SystemUI bindPowerUI(PowerUI sysui);
+
+ /** Inject into StatusBar. */
+ @Binds
+ @IntoMap
+ @ClassKey(Recents.class)
+ public abstract SystemUI bindRecents(Recents sysui);
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index bf81e1d..9958124 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -152,7 +152,8 @@
.setSubtype(Dependency.get(AssistManager.class).toLoggingSubType(type)));
}
// Logs assistant invocation cancelled.
- if (!mInvocationAnimator.isRunning() && invocationWasInProgress && progress == 0f) {
+ if ((mInvocationAnimator == null || !mInvocationAnimator.isRunning())
+ && invocationWasInProgress && progress == 0f) {
if (VERBOSE) {
Log.v(TAG, "Invocation cancelled: type=" + type);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 2bbe775..ca4ec6d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -33,6 +33,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.wakelock.DelayedWakeLock;
@@ -66,7 +67,7 @@
params);
DozeMachine machine = new DozeMachine(wrappedService, config, wakeLock,
- wakefulnessLifecycle, dozeLog);
+ wakefulnessLifecycle, Dependency.get(BatteryController.class), dozeLog);
machine.setParts(new DozeMachine.Part[]{
new DozePauser(handler, machine, alarmManager, params.getPolicy()),
new DozeFalsingManagerAdapter(falsingManager),
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index ba81d2f..75b1d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -27,6 +27,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.Assert;
import com.android.systemui.util.wakelock.WakeLock;
@@ -122,6 +123,7 @@
private final WakeLock mWakeLock;
private final AmbientDisplayConfiguration mConfig;
private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final BatteryController mBatteryController;
private Part[] mParts;
private final ArrayList<State> mQueuedRequests = new ArrayList<>();
@@ -130,11 +132,13 @@
private boolean mWakeLockHeldForCurrentState = false;
public DozeMachine(Service service, AmbientDisplayConfiguration config,
- WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle, DozeLog dozeLog) {
+ WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle,
+ BatteryController batteryController, DozeLog dozeLog) {
mDozeService = service;
mConfig = config;
mWakefulnessLifecycle = wakefulnessLifecycle;
mWakeLock = wakeLock;
+ mBatteryController = batteryController;
mDozeLog = dozeLog;
}
@@ -318,6 +322,9 @@
Log.i(TAG, "Dropping pulse done because current state is already done: " + mState);
return mState;
}
+ if (requestedState == State.DOZE_AOD && mBatteryController.isAodPowerSave()) {
+ return State.DOZE;
+ }
if (requestedState == State.DOZE_REQUEST_PULSE && !mState.canPulse()) {
Log.i(TAG, "Dropping pulse request because current state can't pulse: " + mState);
return mState;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 74dec35..b212884 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -348,7 +348,6 @@
private void checkTriggersAtInit() {
if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR
- || mDozeHost.isPowerSaveActive()
|| mDozeHost.isBlockingDoze()
|| !mDozeHost.isProvisioned()) {
mMachine.requestState(DozeMachine.State.FINISH);
@@ -484,8 +483,8 @@
@Override
public void onPowerSaveChanged(boolean active) {
- if (active) {
- mMachine.requestState(DozeMachine.State.FINISH);
+ if (mDozeHost.isPowerSaveActive()) {
+ mMachine.requestState(DozeMachine.State.DOZE);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 1cfe517..2c0ccd21 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -86,7 +86,8 @@
*/
private void updateAnimateScreenOff() {
if (mCanAnimateTransition) {
- final boolean controlScreenOff = mDozeParameters.getAlwaysOn() && mKeyguardShowing;
+ final boolean controlScreenOff = mDozeParameters.getAlwaysOn() && mKeyguardShowing
+ && !mHost.isPowerSaveActive();
mDozeParameters.setControlScreenOffAnimation(controlScreenOff);
mHost.setAnimateScreenOff(controlScreenOff);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 0fc4fe7..a1b4a93 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -21,25 +21,30 @@
import android.graphics.Rect;
import android.provider.Settings;
-import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.CommandQueue;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import javax.inject.Inject;
+
/**
* A proxy to a Recents implementation.
*/
public class Recents extends SystemUI implements CommandQueue.Callbacks {
- private RecentsImplementation mImpl;
+ private final RecentsImplementation mImpl;
+
+ @Inject
+ public Recents(RecentsImplementation impl) {
+ mImpl = impl;
+ }
@Override
public void start() {
getComponent(CommandQueue.class).addCallback(this);
putComponent(Recents.class, this);
- mImpl = createRecentsImplementationFromConfig();
mImpl.onStart(mContext, this);
}
@@ -139,28 +144,6 @@
(Settings.Secure.getInt(cr, Settings.Secure.USER_SETUP_COMPLETE, 0) != 0);
}
- /**
- * @return The recents implementation from the config.
- */
- private RecentsImplementation createRecentsImplementationFromConfig() {
- final String clsName = mContext.getString(R.string.config_recentsComponent);
- if (clsName == null || clsName.length() == 0) {
- throw new RuntimeException("No recents component configured", null);
- }
- Class<?> cls = null;
- try {
- cls = mContext.getClassLoader().loadClass(clsName);
- } catch (Throwable t) {
- throw new RuntimeException("Error loading recents component: " + clsName, t);
- }
- try {
- RecentsImplementation impl = (RecentsImplementation) cls.newInstance();
- return impl;
- } catch (Throwable t) {
- throw new RuntimeException("Error creating recents component: " + clsName, t);
- }
- }
-
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mImpl.dump(pw);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
new file mode 100644
index 0000000..5555285
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
@@ -0,0 +1,53 @@
+/*
+ * 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.systemui.recents;
+
+import android.content.Context;
+
+import com.android.systemui.R;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger injection module for {@link RecentsImplementation}
+ */
+@Module
+public class RecentsModule {
+ /**
+ * @return The {@link RecentsImplementation} from the config.
+ */
+ @Provides
+ public RecentsImplementation provideRecentsImpl(Context context) {
+ final String clsName = context.getString(R.string.config_recentsComponent);
+ if (clsName == null || clsName.length() == 0) {
+ throw new RuntimeException("No recents component configured", null);
+ }
+ Class<?> cls = null;
+ try {
+ cls = context.getClassLoader().loadClass(clsName);
+ } catch (Throwable t) {
+ throw new RuntimeException("Error loading recents component: " + clsName, t);
+ }
+ try {
+ RecentsImplementation impl = (RecentsImplementation) cls.newInstance();
+ return impl;
+ } catch (Throwable t) {
+ throw new RuntimeException("Error creating recents component: " + clsName, t);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index a70dc7c..e516af5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -27,7 +27,6 @@
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.ViewConfiguration
-import com.android.systemui.Dependency
import com.android.systemui.Gefingerpoken
import com.android.systemui.Interpolators
@@ -58,7 +57,8 @@
private val bypassController: KeyguardBypassController,
private val headsUpManager: HeadsUpManagerPhone,
private val roundnessManager: NotificationRoundnessManager,
- private val statusBarStateController: StatusBarStateController
+ private val statusBarStateController: StatusBarStateController,
+ private val falsingManager: FalsingManager
) : Gefingerpoken {
companion object {
private val RUBBERBAND_FACTOR_STATIC = 0.25f
@@ -99,7 +99,6 @@
private val mTemp2 = IntArray(2)
private var mDraggedFarEnough: Boolean = false
private var mStartingChild: ExpandableView? = null
- private val mFalsingManager: FalsingManager
private var mPulsing: Boolean = false
var isWakingToShadeLocked: Boolean = false
private set
@@ -109,7 +108,7 @@
private var velocityTracker: VelocityTracker? = null
private val isFalseTouch: Boolean
- get() = mFalsingManager.isFalseTouch
+ get() = falsingManager.isFalseTouch
var qsExpanded: Boolean = false
var pulseExpandAbortListener: Runnable? = null
var bouncerShowing: Boolean = false
@@ -118,7 +117,6 @@
mMinDragDistance = context.resources.getDimensionPixelSize(
R.dimen.keyguard_drag_down_min_distance)
mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop.toFloat()
- mFalsingManager = Dependency.get(FalsingManager::class.java)
mPowerManager = context.getSystemService(PowerManager::class.java)
}
@@ -151,7 +149,7 @@
MotionEvent.ACTION_MOVE -> {
val h = y - mInitialTouchY
if (h > mTouchSlop && h > Math.abs(x - mInitialTouchX)) {
- mFalsingManager.onStartExpandingFromPulse()
+ falsingManager.onStartExpandingFromPulse()
isExpanding = true
captureStartingChild(mInitialTouchX, mInitialTouchY)
mInitialTouchY = y
@@ -192,7 +190,7 @@
velocityTracker!!.computeCurrentVelocity(1000 /* units */)
val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 &&
statusBarStateController.state != StatusBarState.SHADE
- if (!mFalsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
+ if (!falsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
finishExpansion()
} else {
cancelExpansion()
@@ -297,7 +295,7 @@
private fun cancelExpansion() {
isExpanding = false
- mFalsingManager.onExpansionFromPulseStopped()
+ falsingManager.onExpansionFromPulseStopped()
if (mStartingChild != null) {
reset(mStartingChild!!)
mStartingChild = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
index 9362d2d..eadec6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -39,6 +39,7 @@
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import javax.inject.Inject;
@@ -63,6 +64,7 @@
private final Context mContext;
private final PowerManager mPowerManager;
private final IDreamManager mDreamManager;
+ private final BatteryController mBatteryController;
private NotificationPresenter mPresenter;
private HeadsUpManager mHeadsUpManager;
@@ -75,13 +77,14 @@
@Inject
public NotificationInterruptionStateProvider(Context context, NotificationFilter filter,
- StatusBarStateController stateController) {
+ StatusBarStateController stateController, BatteryController batteryController) {
this(context,
(PowerManager) context.getSystemService(Context.POWER_SERVICE),
IDreamManager.Stub.asInterface(
ServiceManager.checkService(DreamService.DREAM_SERVICE)),
new AmbientDisplayConfiguration(context),
filter,
+ batteryController,
stateController);
}
@@ -92,10 +95,12 @@
IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
NotificationFilter notificationFilter,
+ BatteryController batteryController,
StatusBarStateController statusBarStateController) {
mContext = context;
mPowerManager = powerManager;
mDreamManager = dreamManager;
+ mBatteryController = batteryController;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mNotificationFilter = notificationFilter;
mStatusBarStateController = statusBarStateController;
@@ -293,6 +298,13 @@
return false;
}
+ if (mBatteryController.isAodPowerSave()) {
+ if (DEBUG_HEADS_UP) {
+ Log.d(TAG, "No pulsing: disabled by battery saver: " + sbn.getKey());
+ }
+ return false;
+ }
+
if (!canAlertCommon(entry)) {
if (DEBUG_HEADS_UP) {
Log.d(TAG, "No pulsing: notification shouldn't alert: " + sbn.getKey());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 8d73251..a817f54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -180,6 +180,10 @@
initDimens();
}
+ public FalsingManager getFalsingManager() {
+ return mFalsingManager;
+ }
+
private void updateColors() {
mNormalColor = mContext.getColor(R.color.notification_material_background_color);
mTintedRippleColor = mContext.getColor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 0f6ce21..9f4b026 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -74,7 +74,6 @@
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
@@ -221,7 +220,6 @@
private ViewStub mGutsStub;
private boolean mIsSystemChildExpanded;
private boolean mIsPinned;
- private FalsingManager mFalsingManager;
private boolean mExpandAnimationRunning;
private AboveShelfChangedListener mAboveShelfChangedListener;
private HeadsUpManager mHeadsUpManager;
@@ -1636,7 +1634,6 @@
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
- mFalsingManager = Dependency.get(FalsingManager.class); // TODO: inject into a controller.
mNotificationInflater = new NotificationContentInflater(this);
mMenuRow = new NotificationMenuRow(mContext);
mImageResolver = new NotificationInlineImageResolver(context,
@@ -2208,7 +2205,7 @@
* @param allowChildExpansion whether a call to this method allows expanding children
*/
public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
- mFalsingManager.setNotificationExpanded();
+ getFalsingManager().setNotificationExpanded();
if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion
&& !mChildrenContainer.showingAsLowPriority()) {
final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
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 8150161..5fc2d9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -396,6 +396,8 @@
boolean mAllowNotificationLongPress;
@Inject
protected NotifPipelineInitializer mNotifPipelineInitializer;
+ @Inject
+ protected FalsingManager mFalsingManager;
@VisibleForTesting
BroadcastDispatcher mBroadcastDispatcher;
@@ -587,7 +589,6 @@
}
};
private boolean mNoAnimationOnNextBarModeChange;
- protected FalsingManager mFalsingManager;
private final SysuiStatusBarStateController mStatusBarStateController =
(SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
@@ -719,7 +720,6 @@
mRecents = getComponent(Recents.class);
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mFalsingManager = Dependency.get(FalsingManager.class);
// Connect in to the status bar manager service
mCommandQueue = getComponent(CommandQueue.class);
@@ -2448,7 +2448,7 @@
mKeyguardUpdateMonitor.dump(fd, pw, args);
}
- Dependency.get(FalsingManager.class).dump(pw);
+ mFalsingManager.dump(pw);
FalsingLog.dump(pw);
pw.println("SharedPreferences:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 111cdd2..738d076 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -45,9 +45,7 @@
/**
* Returns {@code true} if AOD was disabled by power saving policies.
*/
- default boolean isAodPowerSave() {
- return isPowerSave();
- }
+ boolean isAodPowerSave();
/**
* A listener that will be notified whenever a change in battery level or power save mode has
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 448c80e..2798c6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -74,6 +74,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -174,7 +175,8 @@
TestableNotificationInterruptionStateProvider interruptionStateProvider =
new TestableNotificationInterruptionStateProvider(mContext,
mock(NotificationFilter.class),
- mock(StatusBarStateController.class));
+ mock(StatusBarStateController.class),
+ mock(BatteryController.class));
interruptionStateProvider.setUpWithPresenter(
mock(NotificationPresenter.class),
mock(HeadsUpManager.class),
@@ -660,8 +662,9 @@
NotificationInterruptionStateProvider {
TestableNotificationInterruptionStateProvider(Context context,
- NotificationFilter filter, StatusBarStateController controller) {
- super(context, filter, controller);
+ NotificationFilter filter, StatusBarStateController controller,
+ BatteryController batteryController) {
+ super(context, filter, controller, batteryController);
mUseHeadsUp = true;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index 9d42b75..bbd2ab1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.wakelock.WakeLockFake;
import org.junit.Before;
@@ -79,9 +80,7 @@
mPartMock = mock(DozeMachine.Part.class);
mMachine = new DozeMachine(mServiceFake, mConfigMock, mWakeLockFake,
- mWakefulnessLifecycle,
- mDozeLog);
-
+ mWakefulnessLifecycle, mock(BatteryController.class), mDozeLog);
mMachine.setParts(new DozeMachine.Part[]{mPartMock});
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
index 350ab5a..28a7e40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
@@ -52,6 +52,7 @@
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
@@ -84,6 +85,8 @@
HeadsUpManager mHeadsUpManager;
@Mock
NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor;
+ @Mock
+ BatteryController mBatteryController;
private NotificationInterruptionStateProvider mNotifInterruptionStateProvider;
@@ -97,7 +100,8 @@
mDreamManager,
mAmbientDisplayConfiguration,
mNotificationFilter,
- mStatusBarStateController);
+ mStatusBarStateController,
+ mBatteryController);
mNotifInterruptionStateProvider.setUpWithPresenter(
mPresenter,
@@ -590,17 +594,17 @@
/**
* Testable class overriding constructor.
*/
- public class TestableNotificationInterruptionStateProvider extends
+ public static class TestableNotificationInterruptionStateProvider extends
NotificationInterruptionStateProvider {
TestableNotificationInterruptionStateProvider(Context context,
PowerManager powerManager, IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
NotificationFilter notificationFilter,
- StatusBarStateController statusBarStateController) {
+ StatusBarStateController statusBarStateController,
+ BatteryController batteryController) {
super(context, powerManager, dreamManager, ambientDisplayConfiguration,
- notificationFilter,
- statusBarStateController);
+ notificationFilter, batteryController, statusBarStateController);
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index b5ef716..f1da4e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.doze.DozeLog;
+import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.plugins.PluginManager;
@@ -130,9 +131,13 @@
mock(HeadsUpManagerPhone.class),
new StatusBarStateControllerImpl(),
mKeyguardBypassController);
- PulseExpansionHandler expansionHandler = new PulseExpansionHandler(mContext, coordinator,
+ PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
+ mContext,
+ coordinator,
mKeyguardBypassController, mHeadsUpManager,
- mock(NotificationRoundnessManager.class), mStatusBarStateController);
+ mock(NotificationRoundnessManager.class),
+ mStatusBarStateController,
+ new FalsingManagerFake());
mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler,
mKeyguardBypassController);
mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
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 9932a14..32b0f76 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
@@ -113,6 +113,7 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -159,6 +160,7 @@
@Mock private NotificationRemoteInputManager mRemoteInputManager;
@Mock private RemoteInputController mRemoteInputController;
@Mock private StatusBarStateControllerImpl mStatusBarStateController;
+ @Mock private BatteryController mBatteryController;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private StatusBarNotificationPresenter mNotificationPresenter;
@Mock
@@ -214,7 +216,7 @@
mNotificationInterruptionStateProvider =
new TestableNotificationInterruptionStateProvider(mContext, mPowerManager,
mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter,
- mStatusBarStateController);
+ mStatusBarStateController, mBatteryController);
mDependency.injectTestDependency(NotificationInterruptionStateProvider.class,
mNotificationInterruptionStateProvider);
mDependency.injectMockDependency(NavigationBarController.class);
@@ -906,9 +908,10 @@
IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
NotificationFilter filter,
- StatusBarStateController controller) {
+ StatusBarStateController controller,
+ BatteryController batteryController) {
super(context, powerManager, dreamManager, ambientDisplayConfiguration, filter,
- controller);
+ batteryController, controller);
mUseHeadsUp = true;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index a843cca..df76f01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -48,4 +48,9 @@
public boolean isPowerSave() {
return false;
}
+
+ @Override
+ public boolean isAodPowerSave() {
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 5947c35..ecbbef1 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -208,6 +208,10 @@
private Map<Integer, List<EmergencyNumber>> mEmergencyNumberList;
+ private EmergencyNumber[] mOutgoingSmsEmergencyNumber;
+
+ private EmergencyNumber[] mOutgoingCallEmergencyNumber;
+
private CallQuality[] mCallQuality;
private CallAttributes[] mCallAttributes;
@@ -266,6 +270,10 @@
PhoneStateListener.LISTEN_PRECISE_CALL_STATE |
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE;
+ static final int READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK =
+ PhoneStateListener.LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER
+ | PhoneStateListener.LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER;
+
private static final int MSG_USER_SWITCHED = 1;
private static final int MSG_UPDATE_DEFAULT_SUB = 2;
@@ -406,6 +414,8 @@
mImsReasonInfo = new ArrayList<>();
mPhysicalChannelConfigs = new ArrayList<>();
mEmergencyNumberList = new HashMap<>();
+ mOutgoingCallEmergencyNumber = new EmergencyNumber[numPhones];
+ mOutgoingSmsEmergencyNumber = new EmergencyNumber[numPhones];
for (int i = 0; i < numPhones; i++) {
mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE;
@@ -1910,6 +1920,56 @@
}
@Override
+ public void notifyOutgoingEmergencyCall(int phoneId, int subId,
+ EmergencyNumber emergencyNumber) {
+ if (!checkNotifyPermission("notifyOutgoingEmergencyCall()")) {
+ return;
+ }
+ synchronized (mRecords) {
+ if (validatePhoneId(phoneId)) {
+ mOutgoingCallEmergencyNumber[phoneId] = emergencyNumber;
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onOutgoingEmergencyCall(emergencyNumber);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
+ @Override
+ public void notifyOutgoingEmergencySms(int phoneId, int subId,
+ EmergencyNumber emergencyNumber) {
+ if (!checkNotifyPermission("notifyOutgoingEmergencySms()")) {
+ return;
+ }
+ synchronized (mRecords) {
+ if (validatePhoneId(phoneId)) {
+ mOutgoingSmsEmergencyNumber[phoneId] = emergencyNumber;
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onOutgoingEmergencySms(emergencyNumber);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
+ @Override
public void notifyCallQualityChanged(CallQuality callQuality, int phoneId, int subId,
int callNetworkType) {
if (!checkNotifyPermission("notifyCallQualityChanged()")) {
@@ -1981,6 +2041,8 @@
pw.println("mCallAttributes=" + mCallAttributes[i]);
pw.println("mCallNetworkType=" + mCallNetworkType[i]);
pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState[i]);
+ pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
+ pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]);
pw.decreaseIndent();
}
pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState);
@@ -2250,6 +2312,11 @@
android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
}
+ if ((events & READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK) != 0) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION, null);
+ }
+
if ((events & PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT) != 0) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index b1c7c76..9eb0d50 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -930,7 +930,8 @@
final Face face = new Face("", 0 /* identifier */, deviceId);
FaceService.super.handleRemoved(face, 0 /* remaining */);
}
-
+ Settings.Secure.putIntForUser(getContext().getContentResolver(),
+ Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, UserHandle.USER_CURRENT);
});
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 75b9705..8fbad4c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -220,6 +220,8 @@
private static native void nativeSetFocusedApplication(long ptr,
int displayId, InputApplicationHandle application);
private static native void nativeSetFocusedDisplay(long ptr, int displayId);
+ private static native boolean nativeTransferTouchFocus(long ptr,
+ InputChannel fromChannel, InputChannel toChannel);
private static native void nativeSetPointerSpeed(long ptr, int speed);
private static native void nativeSetShowTouches(long ptr, boolean enabled);
private static native void nativeSetInteractive(long ptr, boolean interactive);
@@ -1533,6 +1535,29 @@
nativeSetSystemUiVisibility(mPtr, visibility);
}
+ /**
+ * Atomically transfers touch focus from one window to another as identified by
+ * their input channels. It is possible for multiple windows to have
+ * touch focus if they support split touch dispatch
+ * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
+ * method only transfers touch focus of the specified window without affecting
+ * other windows that may also have touch focus at the same time.
+ * @param fromChannel The channel of a window that currently has touch focus.
+ * @param toChannel The channel of the window that should receive touch focus in
+ * place of the first.
+ * @return True if the transfer was successful. False if the window with the
+ * specified channel did not actually have touch focus at the time of the request.
+ */
+ public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) {
+ if (fromChannel == null) {
+ throw new IllegalArgumentException("fromChannel must not be null.");
+ }
+ if (toChannel == null) {
+ throw new IllegalArgumentException("toChannel must not be null.");
+ }
+ return nativeTransferTouchFocus(mPtr, fromChannel, toChannel);
+ }
+
@Override // Binder call
public void tryPointerSpeed(int speed) {
if (!checkCallingPermission(android.Manifest.permission.SET_POINTER_SPEED,
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index b7eca29..378d9eb 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -44,6 +44,7 @@
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DeviceStateCache;
import android.app.admin.PasswordMetrics;
import android.app.backup.BackupManager;
import android.app.trust.IStrongAuthTracker;
@@ -76,6 +77,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManagerInternal;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.provider.Settings;
@@ -429,6 +431,10 @@
return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
}
+ public UserManagerInternal getUserManagerInternal() {
+ return LocalServices.getService(UserManagerInternal.class);
+ }
+
/**
* Return the {@link DevicePolicyManager} object.
*
@@ -440,6 +446,10 @@
return (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
}
+ public DeviceStateCache getDeviceStateCache() {
+ return DeviceStateCache.getInstance();
+ }
+
public KeyStore getKeyStore() {
return KeyStore.getInstance();
}
@@ -1023,11 +1033,16 @@
}
private void notifySeparateProfileChallengeChanged(int userId) {
- final DevicePolicyManagerInternal dpmi = LocalServices.getService(
- DevicePolicyManagerInternal.class);
- if (dpmi != null) {
- dpmi.reportSeparateProfileChallengeChanged(userId);
- }
+ // LSS cannot call into DPM directly, otherwise it will cause deadlock.
+ // In this case, calling DPM on a handler thread is OK since DPM doesn't
+ // expect reportSeparateProfileChallengeChanged() to happen synchronously.
+ mHandler.post(() -> {
+ final DevicePolicyManagerInternal dpmi = LocalServices.getService(
+ DevicePolicyManagerInternal.class);
+ if (dpmi != null) {
+ dpmi.reportSeparateProfileChallengeChanged(userId);
+ }
+ });
}
@Override
@@ -2038,7 +2053,6 @@
* reporting the password changed.
*/
private void notifyPasswordChanged(@UserIdInt int userId) {
- // Same handler as notifyActivePasswordMetricsAvailable to ensure correct ordering
mHandler.post(() -> {
mInjector.getDevicePolicyManager().reportPasswordChanged(userId);
LocalServices.getService(WindowManagerInternal.class).reportPasswordChanged(userId);
@@ -3026,45 +3040,43 @@
pw.decreaseIndent();
}
+ /**
+ * Cryptographically disable escrow token support for the current user, if the user is not
+ * managed (either user has a profile owner, or if device is managed). Do not disable
+ * if we are running an automotive build.
+ */
private void disableEscrowTokenOnNonManagedDevicesIfNeeded(int userId) {
- long ident = Binder.clearCallingIdentity();
- try {
- // Managed profile should have escrow enabled
- if (mUserManager.getUserInfo(userId).isManagedProfile()) {
- Slog.i(TAG, "Managed profile can have escrow token");
- return;
- }
- DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
- // Devices with Device Owner should have escrow enabled on all users.
- if (dpm.getDeviceOwnerComponentOnAnyUser() != null) {
- Slog.i(TAG, "Corp-owned device can have escrow token");
- return;
- }
- // We could also have a profile owner on the given (non-managed) user for unicorn cases
- if (dpm.getProfileOwnerAsUser(userId) != null) {
- Slog.i(TAG, "User with profile owner can have escrow token");
- return;
- }
- // If the device is yet to be provisioned (still in SUW), there is still
- // a chance that Device Owner will be set on the device later, so postpone
- // disabling escrow token for now.
- if (!dpm.isDeviceProvisioned()) {
- Slog.i(TAG, "Postpone disabling escrow tokens until device is provisioned");
- return;
- }
+ final UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
- // Escrow tokens are enabled on automotive builds.
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
- return;
- }
+ // Managed profile should have escrow enabled
+ if (userManagerInternal.isUserManaged(userId)) {
+ Slog.i(TAG, "Managed profile can have escrow token");
+ return;
+ }
- // Disable escrow token permanently on all other device/user types.
- Slog.i(TAG, "Disabling escrow token on user " + userId);
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- mSpManager.destroyEscrowData(userId);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
+ // Devices with Device Owner should have escrow enabled on all users.
+ if (userManagerInternal.isDeviceManaged()) {
+ Slog.i(TAG, "Corp-owned device can have escrow token");
+ return;
+ }
+
+ // If the device is yet to be provisioned (still in SUW), there is still
+ // a chance that Device Owner will be set on the device later, so postpone
+ // disabling escrow token for now.
+ if (!mInjector.getDeviceStateCache().isDeviceProvisioned()) {
+ Slog.i(TAG, "Postpone disabling escrow tokens until device is provisioned");
+ return;
+ }
+
+ // Escrow tokens are enabled on automotive builds.
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ return;
+ }
+
+ // Disable escrow token permanently on all other device/user types.
+ Slog.i(TAG, "Disabling escrow token on user " + userId);
+ if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+ mSpManager.destroyEscrowData(userId);
}
}
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 4f47afe..c404dad 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -78,6 +78,13 @@
sMacPermissions.add(new File(
Environment.getRootDirectory(), "/etc/selinux/plat_mac_permissions.xml"));
+ // SystemExt mac permissions (optional).
+ final File systemExtMacPermission = new File(
+ Environment.getSystemExtDirectory(), "/etc/selinux/system_ext_mac_permissions.xml");
+ if (systemExtMacPermission.exists()) {
+ sMacPermissions.add(systemExtMacPermission);
+ }
+
// Product mac permissions (optional).
final File productMacPermission = new File(
Environment.getProductDirectory(), "/etc/selinux/product_mac_permissions.xml");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5f86708..dace598 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3979,6 +3979,13 @@
}
@Override
+ public boolean isDeviceManaged() {
+ synchronized (mUsersLock) {
+ return mIsDeviceManaged;
+ }
+ }
+
+ @Override
public void setUserManaged(@UserIdInt int userId, boolean isManaged) {
synchronized (mUsersLock) {
mIsUserManaged.put(userId, isManaged);
@@ -3986,6 +3993,13 @@
}
@Override
+ public boolean isUserManaged(@UserIdInt int userId) {
+ synchronized (mUsersLock) {
+ return mIsUserManaged.get(userId);
+ }
+ }
+
+ @Override
public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
long ident = Binder.clearCallingIdentity();
try {
@@ -4205,6 +4219,7 @@
return restrictions != null && restrictions.getBoolean(restrictionKey);
}
+ @Override
public @Nullable UserInfo getUserInfo(@UserIdInt int userId) {
UserData userData;
synchronized (mUsersLock) {
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 7953c50..fc8d520 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1302,7 +1302,7 @@
}
if (bp.isSoftRestricted() && !SoftRestrictedPermissionPolicy.forPermission(mContext,
- pkg, UserHandle.of(userId), permName).canBeGranted()) {
+ pkg.toAppInfo(), UserHandle.of(userId), permName).mayGrantPermission()) {
Log.e(TAG, "Cannot grant soft restricted permission " + permName + " for package "
+ packageName);
return;
@@ -3361,6 +3361,9 @@
final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
| PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+ final int compatFlags = PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
+
final boolean supportsRuntimePermissions = pkg.getTargetSdkVersion()
>= Build.VERSION_CODES.M;
@@ -3384,12 +3387,11 @@
callingUid, userId, callback);
}
} else {
- // In permission review mode we clear the review flag when we
- // are asked to install the app with all permissions granted.
- if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- updatePermissionFlagsInternal(permission, pkg.getPackageName(),
- PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0, callingUid,
- userId, false, callback);
+ // In permission review mode we clear the review flag and the revoked compat
+ // flag when we are asked to install the app with all permissions granted.
+ if ((flags & compatFlags) != 0) {
+ updatePermissionFlagsInternal(permission, pkg.getPackageName(), compatFlags,
+ 0, callingUid, userId, false, callback);
}
}
}
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index a98de89..2f66713 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -17,13 +17,12 @@
package com.android.server.policy;
import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_NONE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import android.annotation.NonNull;
@@ -157,14 +156,12 @@
appOpsService.startWatchingMode(getSwitchOp(perm.name), null, appOpsListener);
} else if (perm.isSoftRestricted()) {
appOpsService.startWatchingMode(getSwitchOp(perm.name), null, appOpsListener);
-
SoftRestrictedPermissionPolicy policy =
- SoftRestrictedPermissionPolicy.forPermission(null,
- (AndroidPackage) null, null,
+ SoftRestrictedPermissionPolicy.forPermission(null, null, null,
perm.name);
- if (policy.resolveAppOp() != OP_NONE) {
- appOpsService.startWatchingMode(policy.resolveAppOp(), null,
- appOpsListener);
+ int extraAppOp = policy.getExtraAppOpCode();
+ if (extraAppOp != OP_NONE) {
+ appOpsService.startWatchingMode(extraAppOp, null, appOpsListener);
}
}
}
@@ -397,24 +394,6 @@
private final @NonNull SparseIntArray mAllUids = new SparseIntArray();
/**
- * All ops that need to be set to default
- *
- * Currently, only used by the restricted permissions logic.
- *
- * @see #syncPackages
- */
- private final @NonNull ArrayList<OpToChange> mOpsToDefault = new ArrayList<>();
-
- /**
- * All ops that need to be flipped to allow if default.
- *
- * Currently, only used by the restricted permissions logic.
- *
- * @see #syncPackages
- */
- private final @NonNull ArrayList<OpToChange> mOpsToAllowIfDefault = new ArrayList<>();
-
- /**
* All ops that need to be flipped to allow.
*
* @see #syncPackages
@@ -422,15 +401,6 @@
private final @NonNull ArrayList<OpToChange> mOpsToAllow = new ArrayList<>();
/**
- * All ops that need to be flipped to ignore if default.
- *
- * Currently, only used by the restricted permissions logic.
- *
- * @see #syncPackages
- */
- private final @NonNull ArrayList<OpToChange> mOpsToIgnoreIfDefault = new ArrayList<>();
-
- /**
* All ops that need to be flipped to ignore.
*
* @see #syncPackages
@@ -438,6 +408,15 @@
private final @NonNull ArrayList<OpToChange> mOpsToIgnore = new ArrayList<>();
/**
+ * All ops that need to be flipped to ignore if not allowed.
+ *
+ * Currently, only used by soft restricted permissions logic.
+ *
+ * @see #syncPackages
+ */
+ private final @NonNull ArrayList<OpToChange> mOpsToIgnoreIfNotAllowed = new ArrayList<>();
+
+ /**
* All ops that need to be flipped to foreground.
*
* Currently, only used by the foreground/background permissions logic.
@@ -481,19 +460,6 @@
alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
}
- final int allowIfDefaultCount = mOpsToAllowIfDefault.size();
- for (int i = 0; i < allowIfDefaultCount; i++) {
- final OpToChange op = mOpsToAllowIfDefault.get(i);
- if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
- continue;
- }
-
- boolean wasSet = setUidModeAllowedIfDefault(op.code, op.uid, op.packageName);
- if (wasSet) {
- alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
- }
- }
-
final int foregroundIfAllowedCount = mOpsToForegroundIfAllow.size();
for (int i = 0; i < foregroundIfAllowedCount; i++) {
final OpToChange op = mOpsToForegroundIfAllow.get(i);
@@ -529,29 +495,18 @@
alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
}
- final int ignoreIfDefaultCount = mOpsToIgnoreIfDefault.size();
- for (int i = 0; i < ignoreIfDefaultCount; i++) {
- final OpToChange op = mOpsToIgnoreIfDefault.get(i);
+ final int ignoreIfNotAllowedCount = mOpsToIgnoreIfNotAllowed.size();
+ for (int i = 0; i < ignoreIfNotAllowedCount; i++) {
+ final OpToChange op = mOpsToIgnoreIfNotAllowed.get(i);
if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
continue;
}
- boolean wasSet = setUidModeIgnoredIfDefault(op.code, op.uid, op.packageName);
+ boolean wasSet = setUidModeIgnoredIfNotAllowed(op.code, op.uid, op.packageName);
if (wasSet) {
alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
}
}
-
- final int defaultCount = mOpsToDefault.size();
- for (int i = 0; i < defaultCount; i++) {
- final OpToChange op = mOpsToDefault.get(i);
- if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
- continue;
- }
-
- setUidModeDefault(op.code, op.uid, op.packageName);
- alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
- }
}
/**
@@ -573,60 +528,49 @@
return;
}
- final boolean applyRestriction =
- (mPackageManager.getPermissionFlags(permission, pkg.packageName,
- mContext.getUser()) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
-
- if (permissionInfo.isHardRestricted()) {
- if (opCode != OP_NONE) {
- if (applyRestriction) {
- mOpsToDefault.add(new OpToChange(uid, pkg.packageName, opCode));
- } else {
- mOpsToAllowIfDefault.add(new OpToChange(uid, pkg.packageName, opCode));
+ if (opCode != OP_NONE) {
+ int permissionFlags = mPackageManager.getPermissionFlags(permission,
+ pkg.packageName, mContext.getUser());
+ boolean isReviewRequired = (permissionFlags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
+ if (!isReviewRequired) {
+ boolean isRevokedCompat =
+ (permissionFlags & FLAG_PERMISSION_REVOKED_COMPAT) != 0;
+ if (permissionInfo.isHardRestricted()) {
+ boolean shouldApplyRestriction =
+ (permissionFlags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
+ if (isRevokedCompat || shouldApplyRestriction) {
+ mOpsToIgnore.add(new OpToChange(uid, pkg.packageName, opCode));
+ } else {
+ mOpsToAllow.add(new OpToChange(uid, pkg.packageName, opCode));
+ }
+ } else if (permissionInfo.isSoftRestricted()) {
+ SoftRestrictedPermissionPolicy policy =
+ SoftRestrictedPermissionPolicy.forPermission(mContext,
+ pkg.applicationInfo, mContext.getUser(), permission);
+ if (!isRevokedCompat && policy.mayGrantPermission()) {
+ mOpsToAllow.add(new OpToChange(uid, pkg.packageName, opCode));
+ } else {
+ mOpsToIgnore.add(new OpToChange(uid, pkg.packageName, opCode));
+ }
}
}
- } else if (permissionInfo.isSoftRestricted()) {
- final SoftRestrictedPermissionPolicy policy =
+ }
+
+ if (permissionInfo.isSoftRestricted()) {
+ SoftRestrictedPermissionPolicy policy =
SoftRestrictedPermissionPolicy.forPermission(mContext, pkg.applicationInfo,
mContext.getUser(), permission);
-
- if (opCode != OP_NONE) {
- if (policy.canBeGranted()) {
- mOpsToAllowIfDefault.add(new OpToChange(uid, pkg.packageName, opCode));
+ int extraOpCode = policy.getExtraAppOpCode();
+ if (extraOpCode != OP_NONE) {
+ if (policy.mayAllowExtraAppOp()) {
+ mOpsToAllow.add(new OpToChange(uid, pkg.packageName, extraOpCode));
} else {
- mOpsToDefault.add(new OpToChange(uid, pkg.packageName, opCode));
- }
- }
-
- final int op = policy.resolveAppOp();
- if (op != OP_NONE) {
- switch (policy.getDesiredOpMode()) {
- case MODE_DEFAULT:
- mOpsToDefault.add(new OpToChange(uid, pkg.packageName, op));
- break;
- case MODE_ALLOWED:
- if (policy.shouldSetAppOpIfNotDefault()) {
- mOpsToAllow.add(new OpToChange(uid, pkg.packageName, op));
- } else {
- mOpsToAllowIfDefault.add(
- new OpToChange(uid, pkg.packageName, op));
- }
- break;
- case MODE_FOREGROUND:
- Slog.wtf(LOG_TAG,
- "Setting appop to foreground is not implemented");
- break;
- case MODE_IGNORED:
- if (policy.shouldSetAppOpIfNotDefault()) {
- mOpsToIgnore.add(new OpToChange(uid, pkg.packageName, op));
- } else {
- mOpsToIgnoreIfDefault.add(
- new OpToChange(uid, pkg.packageName,
- op));
- }
- break;
- case MODE_ERRORED:
- Slog.wtf(LOG_TAG, "Setting appop to errored is not implemented");
+ if (policy.mayDenyExtraAppOpIfGranted()) {
+ mOpsToIgnore.add(new OpToChange(uid, pkg.packageName, extraOpCode));
+ } else {
+ mOpsToIgnoreIfNotAllowed.add(new OpToChange(uid, pkg.packageName,
+ extraOpCode));
+ }
}
}
}
@@ -745,60 +689,51 @@
}
}
- private boolean setUidModeAllowedIfDefault(int opCode, int uid,
- @NonNull String packageName) {
- return setUidModeIfMode(opCode, uid, MODE_DEFAULT, MODE_ALLOWED, packageName);
- }
-
private void setUidModeAllowed(int opCode, int uid, @NonNull String packageName) {
setUidMode(opCode, uid, MODE_ALLOWED, packageName);
}
private boolean setUidModeForegroundIfAllow(int opCode, int uid,
@NonNull String packageName) {
- return setUidModeIfMode(opCode, uid, MODE_ALLOWED, MODE_FOREGROUND, packageName);
+ final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
+ opCode), uid, packageName);
+ if (currentMode == MODE_ALLOWED) {
+ mAppOpsManager.setUidMode(opCode, uid, MODE_FOREGROUND);
+ return true;
+ }
+ return false;
}
private void setUidModeForeground(int opCode, int uid, @NonNull String packageName) {
setUidMode(opCode, uid, MODE_FOREGROUND, packageName);
}
- private boolean setUidModeIgnoredIfDefault(int opCode, int uid,
- @NonNull String packageName) {
- return setUidModeIfMode(opCode, uid, MODE_DEFAULT, MODE_IGNORED, packageName);
- }
-
private void setUidModeIgnored(int opCode, int uid, @NonNull String packageName) {
setUidMode(opCode, uid, MODE_IGNORED, packageName);
}
+ private boolean setUidModeIgnoredIfNotAllowed(int opCode, int uid,
+ @NonNull String packageName) {
+ final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
+ opCode), uid, packageName);
+ if (currentMode != MODE_ALLOWED) {
+ if (currentMode != MODE_IGNORED) {
+ mAppOpsManager.setUidMode(opCode, uid, MODE_IGNORED);
+ }
+ return true;
+ }
+ return false;
+ }
+
private void setUidMode(int opCode, int uid, int mode,
@NonNull String packageName) {
final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager
.opToPublicName(opCode), uid, packageName);
-
if (currentMode != mode) {
mAppOpsManager.setUidMode(opCode, uid, mode);
}
}
- private boolean setUidModeIfMode(int opCode, int uid, int requiredModeBefore, int newMode,
- @NonNull String packageName) {
- final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager
- .opToPublicName(opCode), uid, packageName);
-
- if (currentMode == requiredModeBefore) {
- mAppOpsManager.setUidMode(opCode, uid, newMode);
- return true;
- }
-
- return false;
- }
-
- private void setUidModeDefault(int opCode, int uid, String packageName) {
- setUidMode(opCode, uid, MODE_DEFAULT, packageName);
- }
-
private class OpToChange {
final int uid;
final @NonNull String packageName;
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
index 34c9258..b0f22e4 100644
--- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -18,9 +18,6 @@
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
import static android.app.AppOpsManager.OP_NONE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
@@ -36,7 +33,6 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.parsing.AndroidPackage;
import android.os.Build;
import android.os.UserHandle;
@@ -56,22 +52,7 @@
private static final SoftRestrictedPermissionPolicy DUMMY_POLICY =
new SoftRestrictedPermissionPolicy() {
@Override
- public int resolveAppOp() {
- return OP_NONE;
- }
-
- @Override
- public int getDesiredOpMode() {
- return MODE_DEFAULT;
- }
-
- @Override
- public boolean shouldSetAppOpIfNotDefault() {
- return false;
- }
-
- @Override
- public boolean canBeGranted() {
+ public boolean mayGrantPermission() {
return true;
}
};
@@ -115,10 +96,8 @@
* Get the policy for a soft restricted permission.
*
* @param context A context to use
- * @param appInfo The application the permission belongs to. Can be {@code null}, but then
- * only {@link #resolveAppOp} will work.
- * @param user The user the app belongs to. Can be {@code null}, but then only
- * {@link #resolveAppOp} will work.
+ * @param appInfo The application the permission belongs to.
+ * @param user The user the app belongs to.
* @param permission The name of the permission
*
* @return The policy for this permission
@@ -131,82 +110,46 @@
// where the restricted state allows the permission but only for accessing the medial
// collections.
case READ_EXTERNAL_STORAGE: {
- final int flags;
- final boolean applyRestriction;
final boolean isWhiteListed;
- final boolean hasRequestedLegacyExternalStorage;
+ boolean shouldApplyRestriction;
final int targetSDK;
+ final boolean hasRequestedLegacyExternalStorage;
if (appInfo != null) {
PackageManager pm = context.getPackageManager();
- flags = pm.getPermissionFlags(permission, appInfo.packageName, user);
- applyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
+ int flags = pm.getPermissionFlags(permission, appInfo.packageName, user);
isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
+ shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
targetSDK = getMinimumTargetSDK(context, appInfo, user);
-
- boolean hasAnyRequestedLegacyExternalStorage =
- appInfo.hasRequestedLegacyExternalStorage();
-
- // hasRequestedLegacyExternalStorage is per package. To make sure two apps in
- // the same shared UID do not fight over what to set, always compute the
- // combined hasRequestedLegacyExternalStorage
- String[] uidPkgs = pm.getPackagesForUid(appInfo.uid);
- if (uidPkgs != null) {
- for (String uidPkg : uidPkgs) {
- if (!uidPkg.equals(appInfo.packageName)) {
- ApplicationInfo uidPkgInfo;
- try {
- uidPkgInfo = pm.getApplicationInfoAsUser(uidPkg, 0, user);
- } catch (PackageManager.NameNotFoundException e) {
- continue;
- }
-
- hasAnyRequestedLegacyExternalStorage |=
- uidPkgInfo.hasRequestedLegacyExternalStorage();
- }
- }
- }
-
- hasRequestedLegacyExternalStorage = hasAnyRequestedLegacyExternalStorage;
+ hasRequestedLegacyExternalStorage = hasUidRequestedLegacyExternalStorage(
+ appInfo.uid, context);
} else {
- flags = 0;
- applyRestriction = false;
isWhiteListed = false;
- hasRequestedLegacyExternalStorage = false;
+ shouldApplyRestriction = false;
targetSDK = 0;
+ hasRequestedLegacyExternalStorage = false;
}
+ // We have a check in PermissionPolicyService.PermissionToOpSynchroniser.setUidMode
+ // to prevent apps losing files in legacy storage, because we are holding the
+ // package manager lock here. If we ever remove this policy that check should be
+ // removed as well.
return new SoftRestrictedPermissionPolicy() {
@Override
- public int resolveAppOp() {
+ public boolean mayGrantPermission() {
+ return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q;
+ }
+ @Override
+ public int getExtraAppOpCode() {
return OP_LEGACY_STORAGE;
}
-
@Override
- public int getDesiredOpMode() {
- if (applyRestriction) {
- return MODE_DEFAULT;
- } else if (hasRequestedLegacyExternalStorage) {
- return MODE_ALLOWED;
- } else {
- return MODE_IGNORED;
- }
+ public boolean mayAllowExtraAppOp() {
+ return !shouldApplyRestriction && hasRequestedLegacyExternalStorage;
}
-
@Override
- public boolean shouldSetAppOpIfNotDefault() {
- // Do not switch from allowed -> ignored as this would mean to retroactively
- // turn on isolated storage. This will make the app loose all its files.
- return getDesiredOpMode() != MODE_IGNORED;
- }
-
- @Override
- public boolean canBeGranted() {
- if (isWhiteListed || targetSDK >= Build.VERSION_CODES.Q) {
- return true;
- } else {
- return false;
- }
+ public boolean mayDenyExtraAppOpIfGranted() {
+ return shouldApplyRestriction;
}
};
}
@@ -226,22 +169,7 @@
return new SoftRestrictedPermissionPolicy() {
@Override
- public int resolveAppOp() {
- return OP_NONE;
- }
-
- @Override
- public int getDesiredOpMode() {
- return MODE_DEFAULT;
- }
-
- @Override
- public boolean shouldSetAppOpIfNotDefault() {
- return false;
- }
-
- @Override
- public boolean canBeGranted() {
+ public boolean mayGrantPermission() {
return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q;
}
};
@@ -251,106 +179,51 @@
}
}
- /**
- * Get the policy for a soft restricted permission.
- *
- * @param context A context to use
- * @param pkg The application the permission belongs to. Can be {@code null}, but then
- * only {@link #resolveAppOp} will work.
- * @param user The user the app belongs to. Can be {@code null}, but then only
- * {@link #resolveAppOp} will work.
- * @param permission The name of the permission
- *
- * @return The policy for this permission
- */
- public static @NonNull SoftRestrictedPermissionPolicy forPermission(@NonNull Context context,
- @Nullable AndroidPackage pkg, @Nullable UserHandle user,
- @NonNull String permission) {
- switch (permission) {
- // Storage uses a special app op to decide the mount state and supports soft restriction
- // where the restricted state allows the permission but only for accessing the medial
- // collections.
- case READ_EXTERNAL_STORAGE:
- case WRITE_EXTERNAL_STORAGE: {
- final int flags;
- final boolean applyRestriction;
- final boolean isWhiteListed;
- final boolean hasRequestedLegacyExternalStorage;
- final int targetSDK;
-
- if (pkg != null) {
- flags = context.getPackageManager().getPermissionFlags(permission,
- pkg.getPackageName(), user);
- applyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
- isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
- hasRequestedLegacyExternalStorage = pkg.hasRequestedLegacyExternalStorage();
- targetSDK = pkg.getTargetSdkVersion();
- } else {
- flags = 0;
- applyRestriction = false;
- isWhiteListed = false;
- hasRequestedLegacyExternalStorage = false;
- targetSDK = 0;
- }
-
- return new SoftRestrictedPermissionPolicy() {
- @Override
- public int resolveAppOp() {
- return OP_LEGACY_STORAGE;
- }
-
- @Override
- public int getDesiredOpMode() {
- if (applyRestriction) {
- return MODE_DEFAULT;
- } else if (hasRequestedLegacyExternalStorage) {
- return MODE_ALLOWED;
- } else {
- return MODE_IGNORED;
- }
- }
-
- @Override
- public boolean shouldSetAppOpIfNotDefault() {
- // Do not switch from allowed -> ignored as this would mean to retroactively
- // turn on isolated storage. This will make the app loose all its files.
- return getDesiredOpMode() != MODE_IGNORED;
- }
-
- @Override
- public boolean canBeGranted() {
- if (isWhiteListed || targetSDK >= Build.VERSION_CODES.Q) {
- return true;
- } else {
- return false;
- }
- }
- };
- }
- default:
- return DUMMY_POLICY;
+ private static boolean hasUidRequestedLegacyExternalStorage(int uid, @NonNull Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ String[] packageNames = packageManager.getPackagesForUid(uid);
+ if (packageNames == null) {
+ return false;
}
+ UserHandle user = UserHandle.getUserHandleForUid(uid);
+ for (String packageName : packageNames) {
+ ApplicationInfo applicationInfo;
+ try {
+ applicationInfo = packageManager.getApplicationInfoAsUser(packageName, 0, user);
+ } catch (PackageManager.NameNotFoundException e) {
+ continue;
+ }
+ if (applicationInfo.hasRequestedLegacyExternalStorage()) {
+ return true;
+ }
+ }
+ return false;
}
/**
+ * @return If the permission can be granted
+ */
+ public abstract boolean mayGrantPermission();
+
+ /**
* @return An app op to be changed based on the state of the permission or
* {@link AppOpsManager#OP_NONE} if not app-op should be set.
*/
- public abstract int resolveAppOp();
+ public int getExtraAppOpCode() {
+ return OP_NONE;
+ }
/**
- * @return The mode the {@link #resolveAppOp() app op} should be in.
+ * @return Whether the {@link #getExtraAppOpCode() app op} may be granted.
*/
- public abstract @AppOpsManager.Mode int getDesiredOpMode();
+ public boolean mayAllowExtraAppOp() {
+ return false;
+ }
/**
- * @return If the {@link #resolveAppOp() app op} should be set even if the app-op is currently
- * not {@link AppOpsManager#MODE_DEFAULT}.
+ * @return Whether the {@link #getExtraAppOpCode() app op} may be denied if was granted.
*/
- public abstract boolean shouldSetAppOpIfNotDefault();
-
- /**
- * @return If the permission can be granted
- */
- public abstract boolean canBeGranted();
+ public boolean mayDenyExtraAppOpIfGranted() {
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index f8f6334..d5f403f 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -113,7 +113,7 @@
final WindowState callingWin = mService.windowForClientLocked(
null, window, false);
- if (callingWin == null) {
+ if (callingWin == null || callingWin.cantReceiveTouchInput()) {
Slog.w(TAG_WM, "Bad requesting window " + window);
return null; // !!! TODO: throw here?
}
@@ -167,8 +167,7 @@
final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
- final SurfaceControl.Transaction transaction =
- callingWin.getPendingTransaction();
+ final SurfaceControl.Transaction transaction = mDragState.mTransaction;
transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
transaction.setPosition(
surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY);
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 17daabf..3c61694 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -120,7 +120,7 @@
// A surface used to catch input events for the drag-and-drop operation.
SurfaceControl mInputSurface;
- private final SurfaceControl.Transaction mTransaction;
+ final SurfaceControl.Transaction mTransaction;
private final Rect mTmpClipRect = new Rect();
@@ -129,7 +129,6 @@
* {@code true} when {@link #closeLocked()} is called.
*/
private boolean mIsClosing;
- IBinder mTransferTouchFromToken;
DragState(WindowManagerService service, DragDropController controller, IBinder token,
SurfaceControl surface, int flags, IBinder localWin) {
@@ -167,12 +166,11 @@
mTmpClipRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
mTransaction.setWindowCrop(mInputSurface, mTmpClipRect);
- mTransaction.transferTouchFocus(mTransferTouchFromToken, h.token);
- mTransferTouchFromToken = null;
- // syncInputWindows here to ensure the input channel isn't removed before the transfer.
+ // syncInputWindows here to ensure the input window info is sent before the
+ // transferTouchFocus is called.
mTransaction.syncInputWindows();
- mTransaction.apply();
+ mTransaction.apply(true);
}
/**
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 42866f9..fc8a27d 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -50,6 +50,7 @@
import android.view.InputEvent;
import android.view.InputWindowHandle;
import android.view.MotionEvent;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -302,7 +303,8 @@
mDisplayContent.getDisplayRotation().pause();
// Notify InputMonitor to take mDragWindowHandle.
- mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
+ mDisplayContent.getInputMonitor().updateInputWindowsImmediately();
+ new SurfaceControl.Transaction().syncInputWindows().apply(true);
mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index 2441954..56b3bba 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -24,7 +24,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Slog;
@@ -51,7 +50,6 @@
private @Nullable TaskPositioner mTaskPositioner;
private final Rect mTmpClipRect = new Rect();
- private IBinder mTransferTouchFromToken;
boolean isPositioningLocked() {
return mTaskPositioner != null;
@@ -104,8 +102,6 @@
mTmpClipRect.set(0, 0, p.x, p.y);
t.setWindowCrop(mInputSurface, mTmpClipRect);
- t.transferTouchFocus(mTransferTouchFromToken, h.token);
- mTransferTouchFromToken = null;
}
boolean startMovingTask(IWindow window, float startX, float startY) {
@@ -168,6 +164,7 @@
mPositioningDisplay = displayContent;
mTaskPositioner = TaskPositioner.create(mService);
+ mTaskPositioner.register(displayContent);
// We need to grab the touch focus so that the touch events during the
// resizing/scrolling are not sent to the app. 'win' is the main window
@@ -178,8 +175,12 @@
&& displayContent.mCurrentFocus.mAppToken == win.mAppToken) {
transferFocusFromWin = displayContent.mCurrentFocus;
}
- mTransferTouchFromToken = transferFocusFromWin.mInputChannel.getToken();
- mTaskPositioner.register(displayContent);
+ if (!mInputManager.transferTouchFocus(
+ transferFocusFromWin.mInputChannel, mTaskPositioner.mServerChannel)) {
+ Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus");
+ cleanUpTaskPositioner();
+ return false;
+ }
mTaskPositioner.startDrag(win, resize, preserveOrientation, startX, startY);
return true;
diff --git a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
index d36ebf0..f291573 100644
--- a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
+++ b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
@@ -38,8 +38,8 @@
private int mWidth;
private int mHeight;
- TaskScreenshotAnimatable(Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory, Task task,
- SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer) {
+ TaskScreenshotAnimatable(Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory,
+ Task task, SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer) {
GraphicBuffer buffer = screenshotBuffer == null
? null : screenshotBuffer.getGraphicBuffer();
mTask = task;
@@ -58,6 +58,8 @@
surface.copyFrom(mSurfaceControl);
surface.attachAndQueueBufferWithColorSpace(buffer, screenshotBuffer.getColorSpace());
surface.release();
+ final float scale = 1.0f * mTask.getBounds().width() / mWidth;
+ mSurfaceControl.setMatrix(scale, 0, 0, scale);
}
getPendingTransaction().show(mSurfaceControl);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index aa2a964..abbd1ab 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -161,9 +161,8 @@
default boolean registerInputChannel(
DragState state, Display display, InputManagerService service,
InputChannel source) {
- state.mTransferTouchFromToken = source.getToken();
state.register(display);
- return true;
+ return service.transferTouchFocus(source, state.getInputChannel());
}
/**
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 0e9da83..fb2fdab 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1570,6 +1570,27 @@
im->setSystemUiVisibility(visibility);
}
+static jboolean nativeTransferTouchFocus(JNIEnv* env,
+ jclass /* clazz */, jlong ptr, jobject fromChannelObj, jobject toChannelObj) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ sp<InputChannel> fromChannel =
+ android_view_InputChannel_getInputChannel(env, fromChannelObj);
+ sp<InputChannel> toChannel =
+ android_view_InputChannel_getInputChannel(env, toChannelObj);
+
+ if (fromChannel == nullptr || toChannel == nullptr) {
+ return JNI_FALSE;
+ }
+
+ if (im->getInputManager()->getDispatcher()->
+ transferTouchFocus(fromChannel->getToken(), toChannel->getToken())) {
+ return JNI_TRUE;
+ } else {
+ return JNI_FALSE;
+ }
+}
+
static void nativeSetPointerSpeed(JNIEnv* /* env */,
jclass /* clazz */, jlong ptr, jint speed) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1763,6 +1784,8 @@
(void*) nativeSetInputDispatchMode },
{ "nativeSetSystemUiVisibility", "(JI)V",
(void*) nativeSetSystemUiVisibility },
+ { "nativeTransferTouchFocus", "(JLandroid/view/InputChannel;Landroid/view/InputChannel;)Z",
+ (void*) nativeTransferTouchFocus },
{ "nativeSetPointerSpeed", "(JI)V",
(void*) nativeSetPointerSpeed },
{ "nativeSetShowTouches", "(JZ)V",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 479dd1e..c3ff285 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -126,6 +126,7 @@
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DeviceStateCache;
import android.app.admin.NetworkEvent;
import android.app.admin.PasswordMetrics;
import android.app.admin.SecurityLog;
@@ -507,6 +508,7 @@
private final OverlayPackagesProvider mOverlayPackagesProvider;
private final DevicePolicyCacheImpl mPolicyCache = new DevicePolicyCacheImpl();
+ private final DeviceStateCacheImpl mStateCache = new DeviceStateCacheImpl();
/**
* Contains (package-user) pairs to remove. An entry (p, u) implies that removal of package p
@@ -2295,6 +2297,9 @@
policy = new DevicePolicyData(userHandle);
mUserData.append(userHandle, policy);
loadSettingsLocked(policy, userHandle);
+ if (userHandle == UserHandle.USER_SYSTEM) {
+ mStateCache.setDeviceProvisioned(policy.mUserSetupComplete);
+ }
}
return policy;
}
@@ -8958,6 +8963,8 @@
pw.println("Encryption Status: " + getEncryptionStatusName(getEncryptionStatus()));
pw.println();
mPolicyCache.dump(pw);
+ pw.println();
+ mStateCache.dump(pw);
}
}
@@ -11169,6 +11176,9 @@
DevicePolicyData policy = getUserData(userHandle);
if (!policy.mUserSetupComplete) {
policy.mUserSetupComplete = true;
+ if (userHandle == UserHandle.USER_SYSTEM) {
+ mStateCache.setDeviceProvisioned(true);
+ }
synchronized (getLockObject()) {
saveSettingsLocked(userHandle);
}
@@ -11481,6 +11491,12 @@
protected DevicePolicyCache getDevicePolicyCache() {
return mPolicyCache;
}
+
+ @Override
+ protected DeviceStateCache getDeviceStateCache() {
+ return mStateCache;
+ }
+
}
private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
@@ -13021,6 +13037,7 @@
Settings.Secure.USER_SETUP_COMPLETE, 0, userId) != 0;
DevicePolicyData policy = getUserData(userId);
policy.mUserSetupComplete = isUserCompleted;
+ mStateCache.setDeviceProvisioned(isUserCompleted);
synchronized (getLockObject()) {
saveSettingsLocked(userId);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java
new file mode 100644
index 0000000..c3cb9b0
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java
@@ -0,0 +1,57 @@
+/*
+ * 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.devicepolicy;
+
+import android.app.admin.DeviceStateCache;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+
+/**
+ * Implementation of {@link DeviceStateCache}, to which {@link DevicePolicyManagerService} pushes
+ * device state.
+ *
+ */
+public class DeviceStateCacheImpl extends DeviceStateCache {
+ /**
+ * Lock object. For simplicity we just always use this as the lock. We could use each object
+ * as a lock object to make it more fine-grained, but that'd make copy-paste error-prone.
+ */
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private boolean mIsDeviceProvisioned = false;
+
+ @Override
+ public boolean isDeviceProvisioned() {
+ return mIsDeviceProvisioned;
+ }
+
+ /** Update the device provisioned flag for USER_SYSTEM */
+ public void setDeviceProvisioned(boolean provisioned) {
+ synchronized (mLock) {
+ mIsDeviceProvisioned = provisioned;
+ }
+ }
+
+ /** Dump content */
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("Device state cache:");
+ pw.increaseIndent();
+ pw.println("Device provisioned: " + mIsDeviceProvisioned);
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 90d0c30..e93e17a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -141,7 +141,6 @@
import com.android.server.security.KeyChainSystemService;
import com.android.server.signedconfig.SignedConfigService;
import com.android.server.soundtrigger.SoundTriggerService;
-import com.android.server.stats.StatsCompanionService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.telecom.TelecomLoaderService;
@@ -202,6 +201,8 @@
"com.android.server.print.PrintManagerService";
private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
"com.android.server.companion.CompanionDeviceManagerService";
+ private static final String STATS_COMPANION_SERVICE_LIFECYCLE_CLASS =
+ "com.android.server.stats.StatsCompanionService$Lifecycle";
private static final String USB_SERVICE_CLASS =
"com.android.server.usb.UsbService$Lifecycle";
private static final String MIDI_SERVICE_CLASS =
@@ -1875,7 +1876,7 @@
// Statsd helper
t.traceBegin("StartStatsCompanionService");
- mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
+ mSystemServiceManager.startService(STATS_COMPANION_SERVICE_LIFECYCLE_CLASS);
t.traceEnd();
// Incidentd and dumpstated helper
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 1f5ebe4..7cece1f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -29,6 +29,7 @@
import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DeviceStateCache;
import android.app.trust.TrustManager;
import android.content.ComponentName;
import android.content.pm.UserInfo;
@@ -37,6 +38,7 @@
import android.os.IProgressListener;
import android.os.RemoteException;
import android.os.UserManager;
+import android.os.UserManagerInternal;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.security.KeyStore;
@@ -91,6 +93,8 @@
FakeGsiService mGsiService;
PasswordSlotManagerTestable mPasswordSlotManager;
RecoverableKeyStoreManager mRecoverableKeyStoreManager;
+ UserManagerInternal mUserManagerInternal;
+ DeviceStateCache mDeviceStateCache;
protected boolean mHasSecureLockScreen;
@Override
@@ -108,6 +112,8 @@
mGsiService = new FakeGsiService();
mPasswordSlotManager = new PasswordSlotManagerTestable();
mRecoverableKeyStoreManager = mock(RecoverableKeyStoreManager.class);
+ mUserManagerInternal = mock(UserManagerInternal.class);
+ mDeviceStateCache = mock(DeviceStateCache.class);
LocalServices.removeServiceForTest(LockSettingsInternal.class);
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
@@ -144,7 +150,8 @@
mAuthSecretService = mock(IAuthSecret.class);
mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils, mStorage,
mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
- mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager);
+ mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager,
+ mUserManagerInternal, mDeviceStateCache);
when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
installChildProfile(MANAGED_PROFILE_USER_ID);
@@ -172,6 +179,9 @@
// Adding a fake Device Owner app which will enable escrow token support in LSS.
when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(
new ComponentName("com.dummy.package", ".FakeDeviceOwner"));
+ when(mUserManagerInternal.isDeviceManaged()).thenReturn(true);
+ when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
+
mLocalService = LocalServices.getService(LockSettingsInternal.class);
}
@@ -184,6 +194,7 @@
when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO);
when(mUserManager.isUserRunning(eq(profileId))).thenReturn(true);
when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(true);
+ when(mUserManagerInternal.isUserManaged(eq(profileId))).thenReturn(true);
return userInfo;
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 5c67d04..65d6f45 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -19,12 +19,14 @@
import static org.mockito.Mockito.mock;
import android.app.IActivityManager;
+import android.app.admin.DeviceStateCache;
import android.content.Context;
import android.hardware.authsecret.V1_0.IAuthSecret;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserManagerInternal;
import android.os.storage.IStorageManager;
import android.security.KeyStore;
import android.security.keystore.KeyPermanentlyInvalidatedException;
@@ -44,15 +46,16 @@
private LockPatternUtils mLockPatternUtils;
private IStorageManager mStorageManager;
private SyntheticPasswordManager mSpManager;
- private IAuthSecret mAuthSecretService;
private FakeGsiService mGsiService;
private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
+ private UserManagerInternal mUserManagerInternal;
+ private DeviceStateCache mDeviceStateCache;
public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
IActivityManager activityManager, LockPatternUtils lockPatternUtils,
IStorageManager storageManager, SyntheticPasswordManager spManager,
- IAuthSecret authSecretService, FakeGsiService gsiService,
- RecoverableKeyStoreManager recoverableKeyStoreManager) {
+ FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
+ UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
super(context);
mLockSettingsStorage = storage;
mKeyStore = keyStore;
@@ -62,6 +65,8 @@
mSpManager = spManager;
mGsiService = gsiService;
mRecoverableKeyStoreManager = recoverableKeyStoreManager;
+ mUserManagerInternal = userManagerInternal;
+ mDeviceStateCache = deviceStateCache;
}
@Override
@@ -93,6 +98,10 @@
public LockPatternUtils getLockPatternUtils() {
return mLockPatternUtils;
}
+ @Override
+ public DeviceStateCache getDeviceStateCache() {
+ return mDeviceStateCache;
+ }
@Override
public KeyStore getKeyStore() {
@@ -110,6 +119,11 @@
}
@Override
+ public UserManagerInternal getUserManagerInternal() {
+ return mUserManagerInternal;
+ }
+
+ @Override
public boolean hasEnrolledBiometrics(int userId) {
return false;
}
@@ -134,10 +148,11 @@
LockSettingsStorage storage, FakeGateKeeperService gatekeeper, KeyStore keystore,
IStorageManager storageManager, IActivityManager mActivityManager,
SyntheticPasswordManager spManager, IAuthSecret authSecretService,
- FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager) {
+ FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
+ UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils,
- storageManager, spManager, authSecretService, gsiService,
- recoverableKeyStoreManager));
+ storageManager, spManager, gsiService,
+ recoverableKeyStoreManager, userManagerInternal, deviceStateCache));
mGateKeeperService = gatekeeper;
mAuthSecretService = authSecretService;
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 127cf49..0776589 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.admin.PasswordMetrics;
import android.os.RemoteException;
@@ -468,6 +469,18 @@
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
}
+ public void testEscrowTokenCannotBeActivatedOnUnmanagedUser() {
+ final byte[] token = "some-high-entropy-secure-token".getBytes();
+ when(mUserManagerInternal.isDeviceManaged()).thenReturn(false);
+ when(mUserManagerInternal.isUserManaged(PRIMARY_USER_ID)).thenReturn(false);
+ when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
+
+ try {
+ mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
+ fail("Escrow token should not be possible on unmanaged device");
+ } catch (SecurityException expected) { }
+ }
+
public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception {
final byte[] password = "password".getBytes();
final byte[] pattern = "123654".getBytes();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 0110e94..bb80e5e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -20,9 +20,11 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -124,6 +126,7 @@
mDisplayContent = spy(mDisplayContent);
mWindow = createDropTargetWindow("Drag test window", 0);
doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
+ when(mWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true);
synchronized (mWm.mGlobalLock) {
mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
@@ -177,6 +180,7 @@
.setFormat(PixelFormat.TRANSLUCENT)
.build();
+ assertTrue(mWm.mInputManager.transferTouchFocus(null, null));
mToken = mTarget.performDrag(
new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
data);
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index 8d2a79b..2ad40f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -97,11 +97,6 @@
}
@Override
- public SurfaceControl.Transaction transferTouchFocus(IBinder fromToken, IBinder toToken) {
- return this;
- }
-
- @Override
public SurfaceControl.Transaction setGeometry(SurfaceControl sc, Rect sourceCrop,
Rect destFrame, @Surface.Rotation int orientation) {
return this;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index 714d2f2..eb351b6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -18,6 +18,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -57,6 +58,10 @@
assertNotNull(mWm.mTaskPositioningController);
mTarget = mWm.mTaskPositioningController;
+ when(mWm.mInputManager.transferTouchFocus(
+ any(InputChannel.class),
+ any(InputChannel.class))).thenReturn(true);
+
mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
mWindow.mInputChannel = new InputChannel();
synchronized (mWm.mGlobalLock) {
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 4fcda4d..7047498 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -302,6 +302,11 @@
void setTestAutoModeApp(String packageName);
/**
+ * @see TelecomServiceImpl#setSystemDialerPackage
+ */
+ void setSystemDialerPackage(in String packageName);
+
+ /**
* @see TelecomServiceImpl#setTestDefaultDialer
*/
void setTestDefaultDialer(in String packageName);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index b449578..1d5c18d 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1739,9 +1739,8 @@
"allow_emergency_video_calls_bool";
/**
- * Flag indicating whether the carrier supports RCS presence indication for video calls. When
- * {@code true}, the carrier supports RCS presence indication for video calls. When presence
- * is supported, the device should use the
+ * Flag indicating whether the carrier supports RCS presence indication for
+ * User Capability Exchange (UCE). When presence is supported, the device should use the
* {@link android.provider.ContactsContract.Data#CARRIER_PRESENCE} bit mask and set the
* {@link android.provider.ContactsContract.Data#CARRIER_PRESENCE_VT_CAPABLE} bit to indicate
* whether each contact supports video calling. The UI is made aware that presence is enabled
@@ -1752,6 +1751,12 @@
public static final String KEY_USE_RCS_PRESENCE_BOOL = "use_rcs_presence_bool";
/**
+ * Flag indicating whether the carrier supports RCS SIP OPTIONS indication for
+ * User Capability Exchange (UCE).
+ */
+ public static final String KEY_USE_RCS_SIP_OPTIONS_BOOL = "use_rcs_sip_options_bool";
+
+ /**
* The duration in seconds that platform call and message blocking is disabled after the user
* contacts emergency services. Platform considers values for below cases:
* 1) 0 <= VALUE <= 604800(one week): the value will be used as the duration directly.
@@ -3435,6 +3440,7 @@
sDefaults.putBoolean(KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL, true);
sDefaults.putInt(KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT, 0);
sDefaults.putBoolean(KEY_USE_RCS_PRESENCE_BOOL, false);
+ sDefaults.putBoolean(KEY_USE_RCS_SIP_OPTIONS_BOOL, false);
sDefaults.putBoolean(KEY_FORCE_IMEI_BOOL, false);
sDefaults.putInt(
KEY_CDMA_ROAMING_MODE_INT, TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT);
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 1ffed25..1e1e3da 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Binder;
import android.os.Build;
@@ -366,6 +367,7 @@
* @hide
*/
@SystemApi
+ @TestApi
@RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
public static final int LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER = 0x10000000;
@@ -378,6 +380,7 @@
* @hide
*/
@SystemApi
+ @TestApi
@RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
public static final int LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER = 0x20000000;
@@ -869,6 +872,7 @@
* to.
* @hide
*/
+ @SystemApi
public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber) {
// default implementation empty
}
@@ -879,6 +883,7 @@
* @param sentEmergencyNumber the emergency number {@link EmergencyNumber} the SMS is sent to.
* @hide
*/
+ @SystemApi
public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber) {
// default implementation empty
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index fe76b7c..d7a7af1 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -93,6 +93,10 @@
void notifyActiveDataSubIdChanged(int activeDataSubId);
void notifyRadioPowerStateChanged(in int phoneId, in int subId, in int state);
void notifyEmergencyNumberList(in int phoneId, in int subId);
+ void notifyOutgoingEmergencyCall(in int phoneId, in int subId,
+ in EmergencyNumber emergencyNumber);
+ void notifyOutgoingEmergencySms(in int phoneId, in int subId,
+ in EmergencyNumber emergencyNumber);
void notifyCallQualityChanged(in CallQuality callQuality, int phoneId, int subId,
int callNetworkType);
void notifyImsDisconnectCause(int subId, in ImsReasonInfo imsReasonInfo);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToAppTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToAppTest.java
new file mode 100644
index 0000000..022f798
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToAppTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.wm.flicker;
+
+import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToApp;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.LargeTest;
+
+import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper;
+
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+
+/**
+ * Test IME window closing back to app window transitions.
+ * To run this test: {@code atest FlickerTests:CloseImeWindowToAppTest}
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class CloseImeAutoOpenWindowToAppTest extends CloseImeWindowToAppTest {
+
+ public CloseImeAutoOpenWindowToAppTest(String beginRotationName, int beginRotation) {
+ super(beginRotationName, beginRotation);
+
+ mTestApp = new ImeAppAutoFocusHelper(InstrumentationRegistry.getInstrumentation());
+ }
+
+ @Before
+ public void runTransition() {
+ run(editTextLoseFocusToApp((ImeAppAutoFocusHelper) mTestApp, mUiDevice, mBeginRotation)
+ .includeJankyRuns().build());
+ }
+
+ @FlakyTest(bugId = 141458352)
+ @Ignore("Waiting bug feedback")
+ @Test
+ public void checkVisibility_imeLayerBecomesInvisible() {
+ super.checkVisibility_imeLayerBecomesInvisible();
+ }
+
+ @FlakyTest(bugId = 141458352)
+ @Ignore("Waiting bug feedback")
+ @Test
+ public void checkVisibility_imeAppLayerIsAlwaysVisible() {
+ super.checkVisibility_imeAppLayerIsAlwaysVisible();
+ }
+
+ @FlakyTest(bugId = 141458352)
+ @Ignore("Waiting bug feedback")
+ @Test
+ public void checkVisibility_imeAppWindowIsAlwaysVisible() {
+ super.checkVisibility_imeAppWindowIsAlwaysVisible();
+ }
+
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToHomeTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToHomeTest.java
new file mode 100644
index 0000000..d6f994b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToHomeTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.wm.flicker;
+
+import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToHome;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.LargeTest;
+
+import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper;
+
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+
+/**
+ * Test IME window closing back to app window transitions.
+ * To run this test: {@code atest FlickerTests:CloseImeWindowToAppTest}
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class CloseImeAutoOpenWindowToHomeTest extends CloseImeWindowToHomeTest {
+
+ public CloseImeAutoOpenWindowToHomeTest(String beginRotationName, int beginRotation) {
+ super(beginRotationName, beginRotation);
+
+ mTestApp = new ImeAppAutoFocusHelper(InstrumentationRegistry.getInstrumentation());
+ }
+
+ @Before
+ public void runTransition() {
+ run(editTextLoseFocusToHome((ImeAppAutoFocusHelper) mTestApp, mUiDevice, mBeginRotation)
+ .includeJankyRuns().build());
+ }
+
+ @FlakyTest(bugId = 141458352)
+ @Ignore("Waiting bug feedback")
+ @Test
+ public void checkVisibility_imeWindowBecomesInvisible() {
+ super.checkVisibility_imeWindowBecomesInvisible();
+ }
+
+ @FlakyTest(bugId = 141458352)
+ @Ignore("Waiting bug feedback")
+ @Test
+ public void checkVisibility_imeLayerBecomesInvisible() {
+ super.checkVisibility_imeLayerBecomesInvisible();
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java
index 9deb977..28da3af 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java
@@ -17,37 +17,39 @@
package com.android.server.wm.flicker;
import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToApp;
-import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
-
-import android.platform.helpers.IAppHelper;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.wm.flicker.helpers.ImeAppHelper;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
/**
* Test IME window closing back to app window transitions.
* To run this test: {@code atest FlickerTests:CloseImeWindowToAppTest}
*/
@LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class CloseImeWindowToAppTest extends FlickerTestBase {
+public class CloseImeWindowToAppTest extends NonRotationTestBase {
- private static final String IME_WINDOW_TITLE = "InputMethod";
- private IAppHelper mImeTestApp = new StandardAppHelper(
- InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "ImeApp");
+ static final String IME_WINDOW_TITLE = "InputMethod";
+
+ public CloseImeWindowToAppTest(String beginRotationName, int beginRotation) {
+ super(beginRotationName, beginRotation);
+
+ mTestApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+ }
@Before
public void runTransition() {
- super.runTransition(editTextLoseFocusToApp(mUiDevice)
+ run(editTextLoseFocusToApp((ImeAppHelper) mTestApp, mUiDevice, mBeginRotation)
.includeJankyRuns().build());
}
@@ -63,20 +65,14 @@
@Test
public void checkVisibility_imeAppLayerIsAlwaysVisible() {
checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(mImeTestApp.getPackage())
+ .showsLayer(mTestApp.getPackage())
.forAllEntries());
}
@Test
public void checkVisibility_imeAppWindowIsAlwaysVisible() {
checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAppWindowOnTop(mImeTestApp.getPackage())
+ .showsAppWindowOnTop(mTestApp.getPackage())
.forAllEntries());
}
-
- @Test
- public void checkCoveredRegion_noUncoveredRegions() {
- checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
- getDisplayBounds()).forAllEntries());
- }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java
index cce5a2a..fc6719e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java
@@ -17,37 +17,39 @@
package com.android.server.wm.flicker;
import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToHome;
-import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
-
-import android.platform.helpers.IAppHelper;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.wm.flicker.helpers.ImeAppHelper;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
/**
* Test IME window closing to home transitions.
* To run this test: {@code atest FlickerTests:CloseImeWindowToHomeTest}
*/
@LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class CloseImeWindowToHomeTest extends FlickerTestBase {
+public class CloseImeWindowToHomeTest extends NonRotationTestBase {
- private static final String IME_WINDOW_TITLE = "InputMethod";
- private IAppHelper mImeTestApp = new StandardAppHelper(
- InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "ImeApp");
+ static final String IME_WINDOW_TITLE = "InputMethod";
+
+ public CloseImeWindowToHomeTest(String beginRotationName, int beginRotation) {
+ super(beginRotationName, beginRotation);
+
+ mTestApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+ }
@Before
public void runTransition() {
- super.runTransition(editTextLoseFocusToHome(mUiDevice)
+ run(editTextLoseFocusToHome((ImeAppHelper) mTestApp, mUiDevice, mBeginRotation)
.includeJankyRuns().build());
}
@@ -72,24 +74,18 @@
@Test
public void checkVisibility_imeAppLayerBecomesInvisible() {
checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(mImeTestApp.getPackage())
+ .showsLayer(mTestApp.getPackage())
.then()
- .hidesLayer(mImeTestApp.getPackage())
+ .hidesLayer(mTestApp.getPackage())
.forAllEntries());
}
@Test
public void checkVisibility_imeAppWindowBecomesInvisible() {
checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAppWindowOnTop(mImeTestApp.getPackage())
+ .showsAppWindowOnTop(mTestApp.getPackage())
.then()
- .hidesAppWindowOnTop(mImeTestApp.getPackage())
+ .hidesAppWindowOnTop(mTestApp.getPackage())
.forAllEntries());
}
-
- @Test
- public void checkCoveredRegion_noUncoveredRegions() {
- checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
- getDisplayBounds()).forAllEntries());
- }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java
index 1d44ea4..fd31aa5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java
@@ -37,10 +37,10 @@
import android.util.Rational;
import android.view.Surface;
-import androidx.test.InstrumentationRegistry;
-
import com.android.server.wm.flicker.TransitionRunner.TransitionBuilder;
import com.android.server.wm.flicker.helpers.AutomationUtils;
+import com.android.server.wm.flicker.helpers.ImeAppHelper;
+import com.android.server.wm.flicker.helpers.PipAppHelper;
/**
* Collection of common transitions which can be used to test different apps or scenarios.
@@ -73,26 +73,17 @@
}
}
- private static void clickEditTextWidget(UiDevice device, IAppHelper testApp) {
- UiObject2 editText = device.findObject(By.res(testApp.getPackage(), "plain_text_input"));
- editText.click();
- sleep(500);
- }
-
- private static void clickEnterPipButton(UiDevice device, IAppHelper testApp) {
- UiObject2 enterPipButton = device.findObject(By.res(testApp.getPackage(), "enter_pip"));
- enterPipButton.click();
- sleep(500);
- }
-
static TransitionBuilder openAppWarm(IAppHelper testApp, UiDevice
- device) {
+ device, int beginRotation) {
return TransitionRunner.newBuilder()
- .withTag("OpenAppWarm_" + testApp.getLauncherName())
+ .withTag("OpenAppWarm_" + testApp.getLauncherName()
+ + rotationToString(beginRotation))
.runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
+ .runBeforeAll(() -> setRotation(device, beginRotation))
.runBeforeAll(testApp::open)
.runBefore(device::pressHome)
.runBefore(device::waitForIdle)
+ .runBefore(() -> setRotation(device, beginRotation))
.run(testApp::open)
.runAfterAll(testApp::exit)
.runAfterAll(AutomationUtils::setDefaultWait)
@@ -127,16 +118,19 @@
.repeat(ITERATIONS);
}
- static TransitionBuilder getOpenAppCold(IAppHelper testApp,
- UiDevice device) {
+ static TransitionBuilder openAppCold(IAppHelper testApp,
+ UiDevice device, int beginRotation) {
return TransitionRunner.newBuilder()
- .withTag("OpenAppCold_" + testApp.getLauncherName())
+ .withTag("OpenAppCold_" + testApp.getLauncherName()
+ + rotationToString(beginRotation))
.runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
.runBefore(device::pressHome)
+ .runBeforeAll(() -> setRotation(device, beginRotation))
.runBefore(testApp::exit)
.runBefore(device::waitForIdle)
.run(testApp::open)
.runAfterAll(testApp::exit)
+ .runAfterAll(() -> setRotation(device, Surface.ROTATION_0))
.repeat(ITERATIONS);
}
@@ -201,28 +195,31 @@
.repeat(ITERATIONS);
}
- static TransitionBuilder editTextSetFocus(UiDevice device) {
- IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "ImeApp");
+ static TransitionBuilder editTextSetFocus(ImeAppHelper testApp, UiDevice device,
+ int beginRotation) {
return TransitionRunner.newBuilder()
- .withTag("editTextSetFocus_" + testApp.getLauncherName())
+ .withTag("editTextSetFocus_" + testApp.getLauncherName()
+ + rotationToString(beginRotation))
.runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
.runBefore(device::pressHome)
+ .runBefore(() -> setRotation(device, beginRotation))
.runBefore(testApp::open)
- .run(() -> clickEditTextWidget(device, testApp))
+ .run(() -> testApp.clickEditTextWidget(device))
.runAfterAll(testApp::exit)
.repeat(ITERATIONS);
}
- static TransitionBuilder resizeSplitScreen(IAppHelper testAppTop, IAppHelper testAppBottom,
- UiDevice device, Rational startRatio, Rational stopRatio) {
+ static TransitionBuilder resizeSplitScreen(IAppHelper testAppTop, ImeAppHelper testAppBottom,
+ UiDevice device, int beginRotation, Rational startRatio, Rational stopRatio) {
String testTag = "resizeSplitScreen_" + testAppTop.getLauncherName() + "_"
+ testAppBottom.getLauncherName() + "_"
+ startRatio.toString().replace("/", ":") + "_to_"
- + stopRatio.toString().replace("/", ":");
+ + stopRatio.toString().replace("/", ":") + "_"
+ + rotationToString(beginRotation);
return TransitionRunner.newBuilder()
.withTag(testTag)
.runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
+ .runBeforeAll(() -> setRotation(device, beginRotation))
.runBeforeAll(() -> clearRecents(device))
.runBefore(testAppBottom::open)
.runBefore(device::pressHome)
@@ -234,6 +231,7 @@
By.res(device.getLauncherPackageName(), "snapshot"));
snapshot.click();
})
+ .runBefore(() -> testAppBottom.clickEditTextWidget(device))
.runBefore(() -> AutomationUtils.resizeSplitScreen(device, startRatio))
.run(() -> AutomationUtils.resizeSplitScreen(device, stopRatio))
.runAfter(() -> exitSplitScreen(device))
@@ -243,74 +241,70 @@
.repeat(ITERATIONS);
}
- static TransitionBuilder editTextLoseFocusToHome(UiDevice device) {
- IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "ImeApp");
+ static TransitionBuilder editTextLoseFocusToHome(ImeAppHelper testApp, UiDevice device,
+ int beginRotation) {
return TransitionRunner.newBuilder()
- .withTag("editTextLoseFocusToHome_" + testApp.getLauncherName())
+ .withTag("editTextLoseFocusToHome_" + testApp.getLauncherName()
+ + rotationToString(beginRotation))
.runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
.runBefore(device::pressHome)
+ .runBefore(() -> setRotation(device, beginRotation))
.runBefore(testApp::open)
- .runBefore(() -> clickEditTextWidget(device, testApp))
+ .runBefore(() -> testApp.clickEditTextWidget(device))
.run(device::pressHome)
.run(device::waitForIdle)
.runAfterAll(testApp::exit)
.repeat(ITERATIONS);
}
- static TransitionBuilder editTextLoseFocusToApp(UiDevice device) {
- IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "ImeApp");
+ static TransitionBuilder editTextLoseFocusToApp(ImeAppHelper testApp, UiDevice device,
+ int beginRotation) {
return TransitionRunner.newBuilder()
- .withTag("editTextLoseFocusToApp_" + testApp.getLauncherName())
+ .withTag("editTextLoseFocusToApp_" + testApp.getLauncherName()
+ + rotationToString(beginRotation))
.runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
.runBefore(device::pressHome)
+ .runBefore(() -> setRotation(device, beginRotation))
.runBefore(testApp::open)
- .runBefore(() -> clickEditTextWidget(device, testApp))
+ .runBefore(() -> testApp.clickEditTextWidget(device))
.run(device::pressBack)
.run(device::waitForIdle)
.runAfterAll(testApp::exit)
.repeat(ITERATIONS);
}
- static TransitionBuilder enterPipMode(UiDevice device) {
- IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "PipApp");
+ static TransitionBuilder enterPipMode(PipAppHelper testApp, UiDevice device) {
return TransitionRunner.newBuilder()
.withTag("enterPipMode_" + testApp.getLauncherName())
.runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
.runBefore(device::pressHome)
.runBefore(testApp::open)
- .run(() -> clickEnterPipButton(device, testApp))
+ .run(() -> testApp.clickEnterPipButton(device))
.runAfter(() -> closePipWindow(device))
.runAfterAll(testApp::exit)
.repeat(ITERATIONS);
}
- static TransitionBuilder exitPipModeToHome(UiDevice device) {
- IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "PipApp");
+ static TransitionBuilder exitPipModeToHome(PipAppHelper testApp, UiDevice device) {
return TransitionRunner.newBuilder()
.withTag("exitPipModeToHome_" + testApp.getLauncherName())
.runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
.runBefore(device::pressHome)
.runBefore(testApp::open)
- .runBefore(() -> clickEnterPipButton(device, testApp))
+ .runBefore(() -> testApp.clickEnterPipButton(device))
.run(() -> closePipWindow(device))
.run(device::waitForIdle)
.runAfterAll(testApp::exit)
.repeat(ITERATIONS);
}
- static TransitionBuilder exitPipModeToApp(UiDevice device) {
- IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "PipApp");
+ static TransitionBuilder exitPipModeToApp(PipAppHelper testApp, UiDevice device) {
return TransitionRunner.newBuilder()
.withTag("exitPipModeToApp_" + testApp.getLauncherName())
.runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
.runBefore(device::pressHome)
.runBefore(testApp::open)
- .runBefore(() -> clickEnterPipButton(device, testApp))
+ .runBefore(() -> testApp.clickEnterPipButton(device))
.run(() -> expandPipWindow(device))
.run(device::waitForIdle)
.runAfterAll(testApp::exit)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java
index 9836655..8f0177c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java
@@ -25,6 +25,9 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.wm.flicker.helpers.ImeAppHelper;
+import com.android.server.wm.flicker.helpers.PipAppHelper;
+
import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.Test;
@@ -44,23 +47,25 @@
private UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
/**
- * atest FlickerTest:DebugTests#openAppCold
+ * atest FlickerTests:DebugTest#openAppCold
*/
@Test
public void openAppCold() {
- CommonTransitions.getOpenAppCold(testApp, uiDevice).recordAllRuns().build().run();
+ CommonTransitions.openAppCold(testApp, uiDevice, Surface.ROTATION_0)
+ .recordAllRuns().build().run();
}
/**
- * atest FlickerTest:DebugTests#openAppWarm
+ * atest FlickerTests:DebugTest#openAppWarm
*/
@Test
public void openAppWarm() {
- CommonTransitions.openAppWarm(testApp, uiDevice).recordAllRuns().build().run();
+ CommonTransitions.openAppWarm(testApp, uiDevice, Surface.ROTATION_0)
+ .recordAllRuns().build().run();
}
/**
- * atest FlickerTest:DebugTests#changeOrientationFromNaturalToLeft
+ * atest FlickerTests:DebugTest#changeOrientationFromNaturalToLeft
*/
@Test
public void changeOrientationFromNaturalToLeft() {
@@ -69,7 +74,7 @@
}
/**
- * atest FlickerTest:DebugTests#closeAppWithBackKey
+ * atest FlickerTests:DebugTest#closeAppWithBackKey
*/
@Test
public void closeAppWithBackKey() {
@@ -77,7 +82,7 @@
}
/**
- * atest FlickerTest:DebugTests#closeAppWithHomeKey
+ * atest FlickerTests:DebugTest#closeAppWithHomeKey
*/
@Test
public void closeAppWithHomeKey() {
@@ -85,7 +90,7 @@
}
/**
- * atest FlickerTest:DebugTests#openAppToSplitScreen
+ * atest FlickerTests:DebugTest#openAppToSplitScreen
*/
@Test
public void openAppToSplitScreen() {
@@ -94,7 +99,7 @@
}
/**
- * atest FlickerTest:DebugTests#splitScreenToLauncher
+ * atest FlickerTests:DebugTest#splitScreenToLauncher
*/
@Test
public void splitScreenToLauncher() {
@@ -104,70 +109,80 @@
}
/**
- * atest FlickerTest:DebugTests#resizeSplitScreen
+ * atest FlickerTests:DebugTest#resizeSplitScreen
*/
@Test
public void resizeSplitScreen() {
- IAppHelper bottomApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "ImeApp");
- CommonTransitions.resizeSplitScreen(testApp, bottomApp, uiDevice, new Rational(1, 3),
- new Rational(2, 3)).includeJankyRuns().recordEachRun().build().run();
+ ImeAppHelper bottomApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+ CommonTransitions.resizeSplitScreen(testApp, bottomApp, uiDevice, Surface.ROTATION_0,
+ new Rational(1, 3), new Rational(2, 3))
+ .includeJankyRuns().recordEachRun().build().run();
}
// IME tests
/**
- * atest FlickerTest:DebugTests#editTextSetFocus
+ * atest FlickerTests:DebugTest#editTextSetFocus
*/
@Test
public void editTextSetFocus() {
- CommonTransitions.editTextSetFocus(uiDevice).includeJankyRuns().recordEachRun()
+ ImeAppHelper testApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+ CommonTransitions.editTextSetFocus(testApp, uiDevice, Surface.ROTATION_0)
+ .includeJankyRuns().recordEachRun()
.build().run();
}
/**
- * atest FlickerTest:DebugTests#editTextLoseFocusToHome
+ * atest FlickerTests:DebugTest#editTextLoseFocusToHome
*/
@Test
public void editTextLoseFocusToHome() {
- CommonTransitions.editTextLoseFocusToHome(uiDevice).includeJankyRuns().recordEachRun()
+ ImeAppHelper testApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+ CommonTransitions.editTextLoseFocusToHome(testApp, uiDevice, Surface.ROTATION_0)
+ .includeJankyRuns().recordEachRun()
.build().run();
}
/**
- * atest FlickerTest:DebugTests#editTextLoseFocusToApp
+ * atest FlickerTests:DebugTest#editTextLoseFocusToApp
*/
@Test
public void editTextLoseFocusToApp() {
- CommonTransitions.editTextLoseFocusToHome(uiDevice).includeJankyRuns().recordEachRun()
+ ImeAppHelper testApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+ CommonTransitions.editTextLoseFocusToHome(testApp, uiDevice, Surface.ROTATION_0)
+ .includeJankyRuns().recordEachRun()
.build().run();
}
// PIP tests
/**
- * atest FlickerTest:DebugTests#enterPipMode
+ * atest FlickerTests:DebugTest#enterPipMode
*/
@Test
public void enterPipMode() {
- CommonTransitions.enterPipMode(uiDevice).includeJankyRuns().recordEachRun().build().run();
- }
-
- /**
- * atest FlickerTest:DebugTests#exitPipModeToHome
- */
- @Test
- public void exitPipModeToHome() {
- CommonTransitions.exitPipModeToHome(uiDevice).includeJankyRuns().recordEachRun()
+ PipAppHelper testApp = new PipAppHelper(InstrumentationRegistry.getInstrumentation());
+ CommonTransitions.enterPipMode(testApp, uiDevice).includeJankyRuns().recordEachRun()
.build().run();
}
/**
- * atest FlickerTest:DebugTests#exitPipModeToApp
+ * atest FlickerTests:DebugTest#exitPipModeToHome
+ */
+ @Test
+ public void exitPipModeToHome() {
+ PipAppHelper testApp = new PipAppHelper(InstrumentationRegistry.getInstrumentation());
+ CommonTransitions.exitPipModeToHome(testApp, uiDevice).includeJankyRuns().recordEachRun()
+ .build().run();
+ }
+
+ /**
+ * atest FlickerTests:DebugTest#exitPipModeToApp
*/
@Test
public void exitPipModeToApp() {
- CommonTransitions.exitPipModeToApp(uiDevice).includeJankyRuns().recordEachRun()
+ PipAppHelper testApp = new PipAppHelper(InstrumentationRegistry.getInstrumentation());
+ CommonTransitions.exitPipModeToApp(testApp, uiDevice).includeJankyRuns().recordEachRun()
.build().run();
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java
index 6e8e0c3..883d59e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java
@@ -98,7 +98,7 @@
/**
* Runs a transition, returns a cached result if the transition has run before.
*/
- void runTransition(TransitionRunner transition) {
+ void run(TransitionRunner transition) {
if (transitionResults.containsKey(transition.getTestTag())) {
mResults = transitionResults.get(transition.getTestTag());
return;
@@ -111,6 +111,13 @@
}
/**
+ * Runs a transition, returns a cached result if the transition has run before.
+ */
+ void runTransition(TransitionRunner transition) {
+ run(transition);
+ }
+
+ /**
* Goes through a list of transition results and checks assertions on each result.
*/
void checkResults(Consumer<TransitionResult> assertion) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.java b/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.java
new file mode 100644
index 0000000..54941dc
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.java
@@ -0,0 +1,80 @@
+/*
+ * 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.wm.flicker;
+
+import static android.view.Surface.rotationToString;
+
+import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
+
+import android.graphics.Rect;
+import android.view.Surface;
+
+import androidx.test.filters.FlakyTest;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public abstract class NonRotationTestBase extends FlickerTestBase {
+
+ int mBeginRotation;
+
+ public NonRotationTestBase(String beginRotationName, int beginRotation) {
+ this.mBeginRotation = beginRotation;
+ }
+
+ @Parameters(name = "{0}")
+ public static Collection<Object[]> getParams() {
+ int[] supportedRotations =
+ {Surface.ROTATION_0, Surface.ROTATION_90};
+ Collection<Object[]> params = new ArrayList<>();
+
+ for (int begin : supportedRotations) {
+ params.add(new Object[]{rotationToString(begin), begin});
+ }
+
+ return params;
+ }
+
+ @FlakyTest(bugId = 141361128)
+ @Ignore("Waiting bug feedback")
+ @Test
+ public void checkCoveredRegion_noUncoveredRegions() {
+ Rect displayBounds = getDisplayBounds(mBeginRotation);
+ checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
+ displayBounds).forAllEntries());
+ }
+
+ @FlakyTest(bugId = 141361128)
+ @Ignore("Waiting bug feedback")
+ @Test
+ public void checkVisibility_navBarLayerIsAlwaysVisible() {
+ checkResults(result -> LayersTraceSubject.assertThat(result)
+ .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
+ }
+
+ @FlakyTest(bugId = 141361128)
+ @Ignore("Waiting bug feedback")
+ @Test
+ public void checkVisibility_statusBarLayerIsAlwaysVisible() {
+ checkResults(result -> LayersTraceSubject.assertThat(result)
+ .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries());
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java
index 8d99054..efdfaee 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java
@@ -16,14 +16,12 @@
package com.android.server.wm.flicker;
-import static com.android.server.wm.flicker.CommonTransitions.getOpenAppCold;
-import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
+import static com.android.server.wm.flicker.CommonTransitions.openAppCold;
import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.FixMethodOrder;
@@ -31,36 +29,28 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
/**
* Test cold launch app from launcher.
* To run this test: {@code atest FlickerTests:OpenAppColdTest}
*/
@LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class OpenAppColdTest extends FlickerTestBase {
+public class OpenAppColdTest extends NonRotationTestBase {
- public OpenAppColdTest() {
+ public OpenAppColdTest(String beginRotationName, int beginRotation) {
+ super(beginRotationName, beginRotation);
+
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
"com.android.server.wm.flicker.testapp", "SimpleApp");
}
@Before
public void runTransition() {
- super.runTransition(getOpenAppCold(mTestApp, mUiDevice).build());
- }
-
- @Test
- public void checkVisibility_navBarWindowIsAlwaysVisible() {
- checkResults(result -> assertThat(result)
- .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
- }
-
- @Test
- public void checkVisibility_statusBarWindowIsAlwaysVisible() {
- checkResults(result -> assertThat(result)
- .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries());
+ run(openAppCold(mTestApp, mUiDevice, mBeginRotation)
+ .includeJankyRuns().build());
}
@Test
@@ -72,6 +62,8 @@
.forAllEntries());
}
+ @FlakyTest(bugId = 140855415)
+ @Ignore("Waiting bug feedback")
@Test
public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
checkResults(result -> assertThat(result)
@@ -83,26 +75,6 @@
}
@Test
- @FlakyTest(bugId = 141235985)
- @Ignore("Waiting bug feedback")
- public void checkCoveredRegion_noUncoveredRegions() {
- checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
- getDisplayBounds()).forAllEntries());
- }
-
- @Test
- public void checkVisibility_navBarLayerIsAlwaysVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
- }
-
- @Test
- public void checkVisibility_statusBarLayerIsAlwaysVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries());
- }
-
- @Test
public void checkVisibility_wallpaperLayerBecomesInvisible() {
checkResults(result -> LayersTraceSubject.assertThat(result)
.showsLayer("Wallpaper")
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java
index e8702c2..7ce6315 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java
@@ -17,13 +17,11 @@
package com.android.server.wm.flicker;
import static com.android.server.wm.flicker.CommonTransitions.openAppWarm;
-import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.FixMethodOrder;
@@ -31,36 +29,28 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
/**
* Test warm launch app.
* To run this test: {@code atest FlickerTests:OpenAppWarmTest}
*/
@LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class OpenAppWarmTest extends FlickerTestBase {
+public class OpenAppWarmTest extends NonRotationTestBase {
- public OpenAppWarmTest() {
+ public OpenAppWarmTest(String beginRotationName, int beginRotation) {
+ super(beginRotationName, beginRotation);
+
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
"com.android.server.wm.flicker.testapp", "SimpleApp");
}
@Before
public void runTransition() {
- super.runTransition(openAppWarm(mTestApp, mUiDevice).includeJankyRuns().build());
- }
-
- @Test
- public void checkVisibility_navBarIsAlwaysVisible() {
- checkResults(result -> assertThat(result)
- .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
- }
-
- @Test
- public void checkVisibility_statusBarIsAlwaysVisible() {
- checkResults(result -> assertThat(result)
- .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries());
+ super.runTransition(openAppWarm(mTestApp, mUiDevice, mBeginRotation)
+ .includeJankyRuns().build());
}
@Test
@@ -72,6 +62,8 @@
.forAllEntries());
}
+ @FlakyTest(bugId = 140855415)
+ @Ignore("Waiting bug feedback")
@Test
public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
checkResults(result -> assertThat(result)
@@ -82,26 +74,6 @@
.forAllEntries());
}
- @FlakyTest(bugId = 141235985)
- @Ignore("Waiting bug feedback")
- @Test
- public void checkCoveredRegion_noUncoveredRegions() {
- checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
- getDisplayBounds()).forAllEntries());
- }
-
- @Test
- public void checkVisibility_navBarLayerIsAlwaysVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
- }
-
- @Test
- public void checkVisibility_statusBarLayerIsAlwaysVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries());
- }
-
@Test
public void checkVisibility_wallpaperLayerBecomesInvisible() {
checkResults(result -> LayersTraceSubject.assertThat(result)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java
index 9f5cfce..91d4a05 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java
@@ -17,31 +17,39 @@
package com.android.server.wm.flicker;
import static com.android.server.wm.flicker.CommonTransitions.editTextSetFocus;
-import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.wm.flicker.helpers.ImeAppHelper;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
/**
* Test IME window opening transitions.
* To run this test: {@code atest FlickerTests:OpenImeWindowTest}
*/
@LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class OpenImeWindowTest extends FlickerTestBase {
+public class OpenImeWindowTest extends NonRotationTestBase {
private static final String IME_WINDOW_TITLE = "InputMethod";
+ public OpenImeWindowTest(String beginRotationName, int beginRotation) {
+ super(beginRotationName, beginRotation);
+
+ mTestApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+ }
+
@Before
public void runTransition() {
- super.runTransition(editTextSetFocus(mUiDevice)
+ run(editTextSetFocus((ImeAppHelper) mTestApp, mUiDevice, mBeginRotation)
.includeJankyRuns().build());
}
@@ -62,10 +70,4 @@
.showsLayer(IME_WINDOW_TITLE)
.forAllEntries());
}
-
- @Test
- public void checkCoveredRegion_noUncoveredRegions() {
- checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
- getDisplayBounds()).forAllEntries());
- }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java
index 1031baf..29b6240 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java
@@ -24,13 +24,13 @@
import static com.google.common.truth.Truth.assertThat;
import android.graphics.Rect;
-import android.platform.helpers.IAppHelper;
import android.util.Rational;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.wm.flicker.helpers.ImeAppHelper;
import org.junit.Before;
import org.junit.FixMethodOrder;
@@ -38,57 +38,48 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
/**
* Test split screen resizing window transitions.
* To run this test: {@code atest FlickerTests:ResizeSplitScreenTest}
*/
@LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 140856143)
+@FlakyTest(bugId = 140854698)
@Ignore("Waiting bug feedback")
-public class ResizeSplitScreenTest extends FlickerTestBase {
+public class ResizeSplitScreenTest extends NonRotationTestBase {
- public ResizeSplitScreenTest() {
+ private static String sSimpleActivity = "SimpleActivity";
+ private static String sImeActivity = "ImeActivity";
+
+ public ResizeSplitScreenTest(String beginRotationName, int beginRotation) {
+ super(beginRotationName, beginRotation);
+
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
"com.android.server.wm.flicker.testapp", "SimpleApp");
}
@Before
public void runTransition() {
- IAppHelper bottomApp = new StandardAppHelper(InstrumentationRegistry
- .getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "ImeApp");
- super.runTransition(resizeSplitScreen(mTestApp, bottomApp, mUiDevice, new Rational(1, 3),
- new Rational(2, 3)).includeJankyRuns().build());
- }
-
- @Test
- public void checkVisibility_navBarLayerIsAlwaysVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
- .forAllEntries());
- }
-
- @Test
- public void checkVisibility_statusBarLayerIsAlwaysVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(STATUS_BAR_WINDOW_TITLE)
- .forAllEntries());
+ ImeAppHelper bottomApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+ run(resizeSplitScreen(mTestApp, bottomApp, mUiDevice, mBeginRotation,
+ new Rational(1, 3), new Rational(2, 3))
+ .includeJankyRuns().build());
}
@Test
public void checkVisibility_topAppLayerIsAlwaysVisible() {
checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer("SimpleActivity")
+ .showsLayer(sSimpleActivity)
.forAllEntries());
}
@Test
public void checkVisibility_bottomAppLayerIsAlwaysVisible() {
checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer("ImeActivity")
+ .showsLayer(sImeActivity)
.forAllEntries());
}
@@ -149,11 +140,11 @@
displayBounds.bottom - getNavigationBarHeight());
LayersTraceSubject.assertThat(result)
- .hasVisibleRegion("SimpleActivity", startingTopAppBounds)
+ .hasVisibleRegion(sSimpleActivity, startingTopAppBounds)
.atTheEnd();
LayersTraceSubject.assertThat(result)
- .hasVisibleRegion("ImeActivity", startingBottomAppBounds)
+ .hasVisibleRegion(sImeActivity, startingBottomAppBounds)
.atTheEnd();
});
}
@@ -175,14 +166,14 @@
@Test
public void checkVisibility_topAppWindowIsAlwaysVisible() {
checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAppWindow("SimpleActivity")
+ .showsAppWindow(sSimpleActivity)
.forAllEntries());
}
@Test
public void checkVisibility_bottomAppWindowIsAlwaysVisible() {
checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAppWindow("ImeActivity")
+ .showsAppWindow(sImeActivity)
.forAllEntries());
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerAppHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerAppHelper.java
new file mode 100644
index 0000000..42977f5
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerAppHelper.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 com.android.server.wm.flicker.helpers;
+
+import android.app.Instrumentation;
+
+import com.android.server.wm.flicker.StandardAppHelper;
+
+public abstract class FlickerAppHelper extends StandardAppHelper {
+
+ static int sFindTimeout = 10000;
+ static String sFlickerPackage = "com.android.server.wm.flicker.testapp";
+
+ public FlickerAppHelper(Instrumentation instr, String launcherName) {
+ super(instr, sFlickerPackage, launcherName);
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.java
new file mode 100644
index 0000000..56e1118
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.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 com.android.server.wm.flicker.helpers;
+
+import android.app.Instrumentation;
+import android.support.test.uiautomator.UiDevice;
+
+public class ImeAppAutoFocusHelper extends ImeAppHelper {
+
+ public ImeAppAutoFocusHelper(Instrumentation instr) {
+ super(instr, "ImeAppAutoFocus");
+ }
+
+ public void clickEditTextWidget(UiDevice device) {
+ // do nothing (the app is focused automatically)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.java
new file mode 100644
index 0000000..098fd6d
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.java
@@ -0,0 +1,41 @@
+/*
+ * 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.wm.flicker.helpers;
+
+import static android.os.SystemClock.sleep;
+
+import android.app.Instrumentation;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+
+public class ImeAppHelper extends FlickerAppHelper {
+
+ ImeAppHelper(Instrumentation instr, String launcherName) {
+ super(instr, launcherName);
+ }
+
+ public ImeAppHelper(Instrumentation instr) {
+ this(instr, "ImeApp");
+ }
+
+ public void clickEditTextWidget(UiDevice device) {
+ UiObject2 editText = device.findObject(By.res(getPackage(), "plain_text_input"));
+ editText.click();
+ sleep(500);
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.java
new file mode 100644
index 0000000..d00e11b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.java
@@ -0,0 +1,43 @@
+/*
+ * 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.wm.flicker.helpers;
+
+import static com.android.server.wm.flicker.helpers.AutomationUtils.getPipWindowSelector;
+
+import android.app.Instrumentation;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+
+public class PipAppHelper extends FlickerAppHelper {
+
+ public PipAppHelper(Instrumentation instr) {
+ super(instr, "PipApp");
+ }
+
+ public void clickEnterPipButton(UiDevice device) {
+ UiObject2 enterPipButton = device.findObject(By.res(getPackage(), "enter_pip"));
+ enterPipButton.click();
+ UiObject2 pipWindow = device.wait(Until.findObject(getPipWindowSelector()), sFindTimeout);
+
+ if (pipWindow == null) {
+ throw new RuntimeException("Unable to find PIP window");
+ }
+ }
+
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index b694172..0fe9682 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -38,6 +38,15 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".ImeActivityAutoFocus"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus"
+ android:windowSoftInputMode="stateVisible"
+ android:label="ImeAppAutoFocus">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
<activity android:name=".PipActivity"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
index d5eb023..4708cfd 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:focusableInTouchMode="true"
android:background="@android:color/holo_green_light">
<EditText android:id="@+id/plain_text_input"
android:layout_height="wrap_content"
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
new file mode 100644
index 0000000..05da717
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
@@ -0,0 +1,30 @@
+/*
+ * 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.wm.flicker.testapp;
+
+import android.widget.EditText;
+
+public class ImeActivityAutoFocus extends ImeActivity {
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ EditText editTextField = findViewById(R.id.plain_text_input);
+ editTextField.requestFocus();
+ }
+}
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index cd2bd26..7029218 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -561,11 +561,17 @@
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{SYSTEM_UID1});
}
- private PackageInfo addPackage(String packageName, int uid, String[] permissions)
+ private PackageInfo setPackagePermissions(String packageName, int uid, String[] permissions)
throws Exception {
PackageInfo packageInfo = packageInfoWithPermissions(permissions, PARTITION_SYSTEM);
when(mPackageManager.getPackageInfo(eq(packageName), anyInt())).thenReturn(packageInfo);
when(mPackageManager.getPackagesForUid(eq(uid))).thenReturn(new String[]{packageName});
+ return packageInfo;
+ }
+
+ private PackageInfo addPackage(String packageName, int uid, String[] permissions)
+ throws Exception {
+ PackageInfo packageInfo = setPackagePermissions(packageName, uid, permissions);
mObserver.onPackageAdded(packageName, uid);
return packageInfo;
}
@@ -616,14 +622,13 @@
}
@Test
- public void testPackageUpdate() throws Exception {
+ public void testPackageRemoveThenAdd() throws Exception {
final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
| INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
- // Remove and install the same package to simulate the update action
when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{});
mObserver.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
@@ -633,6 +638,20 @@
}
@Test
+ public void testPackageUpdate() throws Exception {
+ final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+
+ addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {});
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{MOCK_UID1});
+
+ // When updating a package, the broadcast receiver gets two broadcasts (a remove and then an
+ // add), but the observer sees only one callback (an update).
+ setPackagePermissions(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET});
+ mObserver.onPackageChanged(MOCK_PACKAGE1, MOCK_UID1);
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
+ }
+
+ @Test
public void testPackageUninstallWithMultiplePackages() throws Exception {
final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
diff --git a/tools/codegen/src/com/android/codegen/Main.kt b/tools/codegen/src/com/android/codegen/Main.kt
index fa2b41a..ad5bb9e 100755
--- a/tools/codegen/src/com/android/codegen/Main.kt
+++ b/tools/codegen/src/com/android/codegen/Main.kt
@@ -132,11 +132,11 @@
// $GENERATED_WARNING_PREFIX v$CODEGEN_VERSION.
//
// DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ $cliExecutable ${cliArgs.dropLast(1).joinToString("") { "$it " }}$fileEscaped
- //
- // CHECKSTYLE:OFF Generated code
+ /
"""
if (FeatureFlag.CONST_DEFS()) generateConstDefs()
diff --git a/tools/protologtool/README.md b/tools/protologtool/README.md
index 3439357..ba63957 100644
--- a/tools/protologtool/README.md
+++ b/tools/protologtool/README.md
@@ -8,8 +8,13 @@
### Code transformation
-Command: `process <protolog class path> <protolog implementation class path>
- <protolog groups class path> <config.jar> [<input.java>] <output.srcjar>`
+Command: `protologtool transform-protolog-calls
+ --protolog-class <protolog class name>
+ --protolog-impl-class <protolog implementation class name>
+ --loggroups-class <protolog groups class name>
+ --loggroups-jar <config jar path>
+ --output-srcjar <output.srcjar>
+ [<input.java>]`
In this mode ProtoLogTool transforms every ProtoLog logging call in form of:
```java
@@ -17,16 +22,20 @@
```
into:
```java
-if (GROUP_NAME.isLogToAny()) {
- ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 123456, "Format string %d %s or null", value1, value2);
+if (ProtoLogImpl.isEnabled(GROUP_NAME)) {
+ int protoLogParam0 = value1;
+ String protoLogParam1 = String.valueOf(value2);
+ ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 123456, 0b0100, "Format string %d %s or null", protoLogParam0, protoLogParam1);
}
```
where `ProtoLog`, `ProtoLogImpl` and `ProtoLogGroup` are the classes provided as arguments
(can be imported, static imported or full path, wildcard imports are not allowed) and, `x` is the
logging method. The transformation is done on the source level. A hash is generated from the format
- string and log level and inserted after the `ProtoLogGroup` argument. The format string is replaced
+ string, log level and log group name and inserted after the `ProtoLogGroup` argument. After the hash
+ we insert a bitmask specifying the types of logged parameters. The format string is replaced
by `null` if `ProtoLogGroup.GROUP_NAME.isLogToLogcat()` returns false. If `ProtoLogGroup.GROUP_NAME.isEnabled()`
- returns false the log statement is removed entirely from the resultant code.
+ returns false the log statement is removed entirely from the resultant code. The real generated code is inlined
+ and a number of new line characters is added as to preserve line numbering in file.
Input is provided as a list of java source file names. Transformed source is saved to a single
source jar file. The ProtoLogGroup class with all dependencies should be provided as a compiled
@@ -34,8 +43,12 @@
### Viewer config generation
-Command: `viewerconf <protolog class path> <protolog implementation class path
-<protolog groups class path> <config.jar> [<input.java>] <output.json>`
+Command: `generate-viewer-config
+ --protolog-class <protolog class name>
+ --loggroups-class <protolog groups class name>
+ --loggroups-jar <config jar path>
+ --viewer-conf <viewer.json>
+ [<input.java>]`
This command is similar in it's syntax to the previous one, only instead of creating a processed source jar
it writes a viewer configuration file with following schema:
@@ -46,8 +59,9 @@
"123456": {
"message": "Format string %d %s",
"level": "ERROR",
- "group": "GROUP_NAME"
- },
+ "group": "GROUP_NAME",
+ "at": "com\/android\/server\/example\/Class.java"
+ }
},
"groups": {
"GROUP_NAME": {
@@ -60,13 +74,13 @@
### Binary log viewing
-Command: `read <viewer.json> <wm_log.pb>`
+Command: `read-log --viewer-conf <viewer.json> <wm_log.pb>`
Reads the binary ProtoLog log file and outputs a human-readable LogCat-like text log.
## What is ProtoLog?
-ProtoLog is a logging system created for the WindowManager project. It allows both binary and text logging
+ProtoLog is a generic logging system created for the WindowManager project. It allows both binary and text logging
and is tunable in runtime. It consists of 3 different submodules:
* logging system built-in the Android app,
* log viewer for reading binary logs,
@@ -94,8 +108,7 @@
To add a new logging statement just add a new call to ProtoLog.x where x is a log level.
-After doing any changes to logging groups or statements you should run `make update-protolog` to update
-viewer configuration saved in the code repository.
+After doing any changes to logging groups or statements you should build the project and follow instructions printed by the tool.
## How to change settings on device in runtime?
Use the `adb shell su root cmd window logging` command. To get help just type
diff --git a/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt
index 2e48d97..a52c804 100644
--- a/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt
@@ -32,9 +32,14 @@
.map { c -> c.toInt() }.reduce { h, c -> h * 31 + c }
}
- fun isWildcardStaticImported(code: CompilationUnit, className: String): Boolean {
- return code.findAll(ImportDeclaration::class.java)
- .any { im -> im.isStatic && im.isAsterisk && im.name.toString() == className }
+ fun checkWildcardStaticImported(code: CompilationUnit, className: String, fileName: String) {
+ code.findAll(ImportDeclaration::class.java)
+ .forEach { im ->
+ if (im.isStatic && im.isAsterisk && im.name.toString() == className) {
+ throw IllegalImportException("Wildcard static imports of $className " +
+ "methods are not supported.", ParsingContext(fileName, im))
+ }
+ }
}
fun isClassImportedOrSamePackage(code: CompilationUnit, className: String): Boolean {
@@ -58,24 +63,19 @@
.map { im -> im.name.toString().substringAfterLast('.') }.toSet()
}
- fun concatMultilineString(expr: Expression): String {
+ fun concatMultilineString(expr: Expression, context: ParsingContext): String {
return when (expr) {
is StringLiteralExpr -> expr.asString()
is BinaryExpr -> when {
expr.operator == BinaryExpr.Operator.PLUS ->
- concatMultilineString(expr.left) + concatMultilineString(expr.right)
+ concatMultilineString(expr.left, context) +
+ concatMultilineString(expr.right, context)
else -> throw InvalidProtoLogCallException(
- "messageString must be a string literal " +
- "or concatenation of string literals.", expr)
+ "expected a string literal " +
+ "or concatenation of string literals, got: $expr", context)
}
- else -> throw InvalidProtoLogCallException("messageString must be a string literal " +
- "or concatenation of string literals.", expr)
- }
- }
-
- fun getPositionString(fileName: String): String {
- return when {
- else -> fileName
+ else -> throw InvalidProtoLogCallException("expected a string literal " +
+ "or concatenation of string literals, got: $expr", context)
}
}
}
diff --git a/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt b/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt
index 7759f35..e88f0f8 100644
--- a/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt
@@ -22,7 +22,7 @@
DEBUG, VERBOSE, INFO, WARN, ERROR, WTF;
companion object {
- fun getLevelForMethodName(name: String, node: Node): LogLevel {
+ fun getLevelForMethodName(name: String, node: Node, context: ParsingContext): LogLevel {
return when (name) {
"d" -> DEBUG
"v" -> VERBOSE
@@ -30,7 +30,8 @@
"w" -> WARN
"e" -> ERROR
"wtf" -> WTF
- else -> throw InvalidProtoLogCallException("Unknown log level $name", node)
+ else ->
+ throw InvalidProtoLogCallException("Unknown log level $name in $node", context)
}
}
}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ParsingContext.kt b/tools/protologtool/src/com/android/protolog/tool/ParsingContext.kt
new file mode 100644
index 0000000..c6aedfc
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ParsingContext.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.protolog.tool
+
+import com.github.javaparser.ast.Node
+
+data class ParsingContext(val filePath: String, val lineNumber: Int) {
+ constructor(filePath: String, node: Node)
+ : this(filePath, if (node.range.isPresent) node.range.get().begin.line else -1)
+
+ constructor() : this("", -1)
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt
index eae6396..2181cf6 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt
@@ -38,23 +38,27 @@
private fun getLogGroupName(
expr: Expression,
isClassImported: Boolean,
- staticImports: Set<String>
+ staticImports: Set<String>,
+ fileName: String
): String {
+ val context = ParsingContext(fileName, expr)
return when (expr) {
is NameExpr -> when {
expr.nameAsString in staticImports -> expr.nameAsString
else ->
- throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup", expr)
+ throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
+ context)
}
is FieldAccessExpr -> when {
expr.scope.toString() == protoLogGroupClassName
|| isClassImported &&
expr.scope.toString() == protoLogGroupSimpleClassName -> expr.nameAsString
else ->
- throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup", expr)
+ throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
+ context)
}
else -> throw InvalidProtoLogCallException("Invalid group argument " +
- "- must be ProtoLogGroup enum member reference", expr)
+ "- must be ProtoLogGroup enum member reference: $expr", context)
}
}
@@ -69,12 +73,10 @@
!call.scope.isPresent && staticLogImports.contains(call.name.toString())
}
- open fun process(code: CompilationUnit, callVisitor: ProtoLogCallVisitor?): CompilationUnit {
- if (CodeUtils.isWildcardStaticImported(code, protoLogClassName) ||
- CodeUtils.isWildcardStaticImported(code, protoLogGroupClassName)) {
- throw IllegalImportException("Wildcard static imports of $protoLogClassName " +
- "and $protoLogGroupClassName methods are not supported.")
- }
+ open fun process(code: CompilationUnit, callVisitor: ProtoLogCallVisitor?, fileName: String):
+ CompilationUnit {
+ CodeUtils.checkWildcardStaticImported(code, protoLogClassName, fileName)
+ CodeUtils.checkWildcardStaticImported(code, protoLogGroupClassName, fileName)
val isLogClassImported = CodeUtils.isClassImportedOrSamePackage(code, protoLogClassName)
val staticLogImports = CodeUtils.staticallyImportedMethods(code, protoLogClassName)
@@ -86,22 +88,25 @@
.filter { call ->
isProtoCall(call, isLogClassImported, staticLogImports)
}.forEach { call ->
+ val context = ParsingContext(fileName, call)
if (call.arguments.size < 2) {
throw InvalidProtoLogCallException("Method signature does not match " +
- "any ProtoLog method.", call)
+ "any ProtoLog method: $call", context)
}
- val messageString = CodeUtils.concatMultilineString(call.getArgument(1))
+ val messageString = CodeUtils.concatMultilineString(call.getArgument(1),
+ context)
val groupNameArg = call.getArgument(0)
val groupName =
- getLogGroupName(groupNameArg, isGroupClassImported, staticGroupImports)
+ getLogGroupName(groupNameArg, isGroupClassImported,
+ staticGroupImports, fileName)
if (groupName !in groupMap) {
throw InvalidProtoLogCallException("Unknown group argument " +
- "- not a ProtoLogGroup enum member", call)
+ "- not a ProtoLogGroup enum member: $call", context)
}
callVisitor?.processCall(call, messageString, LogLevel.getLevelForMethodName(
- call.name.toString(), call), groupMap.getValue(groupName))
+ call.name.toString(), call, context), groupMap.getValue(groupName))
}
return code
}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index 53834a6..97f3de2 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -17,7 +17,9 @@
package com.android.protolog.tool
import com.android.protolog.tool.CommandOptions.Companion.USAGE
+import com.github.javaparser.ParseProblemException
import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.CompilationUnit
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
@@ -48,7 +50,7 @@
command.javaSourceArgs.forEach { path ->
val file = File(path)
val text = file.readText()
- val code = StaticJavaParser.parse(text)
+ val code = tryParse(text, path)
val pack = if (code.packageDeclaration.isPresent) code.packageDeclaration
.get().nameAsString else ""
val newPath = pack.replace('.', '/') + '/' + file.name
@@ -66,6 +68,19 @@
out.close()
}
+ private fun tryParse(code: String, fileName: String): CompilationUnit {
+ try {
+ return StaticJavaParser.parse(code)
+ } catch (ex: ParseProblemException) {
+ val problem = ex.problems.first()
+ throw ParsingException("Java parsing erro" +
+ "r: ${problem.verboseMessage}",
+ ParsingContext(fileName, problem.location.orElse(null)
+ ?.begin?.range?.orElse(null)?.begin?.line
+ ?: 0))
+ }
+ }
+
private fun viewerConf(command: CommandOptions) {
val groups = ProtoLogGroupReader()
.loadFromJar(command.protoLogGroupsJarArg, command.protoLogGroupsClassNameArg)
@@ -76,7 +91,7 @@
val file = File(path)
val text = file.readText()
if (containsProtoLogText(text, command.protoLogClassNameArg)) {
- val code = StaticJavaParser.parse(text)
+ val code = tryParse(text, path)
val pack = if (code.packageDeclaration.isPresent) code.packageDeclaration
.get().nameAsString else ""
val newPath = pack.replace('.', '/') + '/' + file.name
@@ -104,8 +119,11 @@
CommandOptions.READ_LOG_CMD -> read(command)
}
} catch (ex: InvalidCommandException) {
- println(ex.message)
+ println("\n${ex.message}\n")
showHelpAndExit()
+ } catch (ex: CodeProcessingException) {
+ println("\n${ex.message}\n")
+ exitProcess(1)
}
}
}
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index c2964a3..00fd038 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -73,8 +73,7 @@
}
val ifStmt: IfStmt
if (group.enabled) {
- val position = CodeUtils.getPositionString(fileName)
- val hash = CodeUtils.hash(position, messageString, level, group)
+ val hash = CodeUtils.hash(fileName, messageString, level, group)
val newCall = call.clone()
if (!group.textEnabled) {
// Remove message string if text logging is not enabled by default.
@@ -99,7 +98,8 @@
NodeList<Expression>(newCall.arguments[0].clone()))
if (argTypes.size != call.arguments.size - 2) {
throw InvalidProtoLogCallException(
- "Number of arguments does not mach format string", call)
+ "Number of arguments (${argTypes.size} does not mach format" +
+ " string in: $call", ParsingContext(fileName, call))
}
val blockStmt = BlockStmt()
if (argTypes.isNotEmpty()) {
@@ -225,7 +225,7 @@
processedCode = code.split('\n').toMutableList()
offsets = IntArray(processedCode.size)
LexicalPreservingPrinter.setup(compilationUnit)
- protoLogCallProcessor.process(compilationUnit, this)
+ protoLogCallProcessor.process(compilationUnit, this, fileName)
// return LexicalPreservingPrinter.print(compilationUnit)
return processedCode.joinToString("\n")
}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
index 172de0e..941455a 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
@@ -32,13 +32,14 @@
group: LogGroup
) {
if (group.enabled) {
- val position = CodeUtils.getPositionString(fileName)
+ val position = fileName
val key = CodeUtils.hash(position, messageString, level, group)
if (statements.containsKey(key)) {
if (statements[key] != LogCall(messageString, level, group, position)) {
throw HashCollisionException(
"Please modify the log message \"$messageString\" " +
- "or \"${statements[key]}\" - their hashes are equal.")
+ "or \"${statements[key]}\" - their hashes are equal.",
+ ParsingContext(fileName, call))
}
} else {
groups.add(group)
@@ -54,7 +55,7 @@
fun processClass(unit: CompilationUnit, fileName: String) {
this.fileName = fileName
- protoLogCallVisitor.process(unit, this)
+ protoLogCallVisitor.process(unit, this, fileName)
}
fun build(): String {
diff --git a/tools/protologtool/src/com/android/protolog/tool/exceptions.kt b/tools/protologtool/src/com/android/protolog/tool/exceptions.kt
index 0401d8f..ae00df1 100644
--- a/tools/protologtool/src/com/android/protolog/tool/exceptions.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/exceptions.kt
@@ -16,16 +16,23 @@
package com.android.protolog.tool
-import com.github.javaparser.ast.Node
import java.lang.Exception
-import java.lang.RuntimeException
-class HashCollisionException(message: String) : RuntimeException(message)
+open class CodeProcessingException(message: String, context: ParsingContext)
+ : Exception("Code processing error in ${context.filePath}:${context.lineNumber}:\n" +
+ " $message")
-class IllegalImportException(message: String) : Exception(message)
+class HashCollisionException(message: String, context: ParsingContext) :
+ CodeProcessingException(message, context)
-class InvalidProtoLogCallException(message: String, node: Node)
- : RuntimeException("$message\nAt: $node")
+class IllegalImportException(message: String, context: ParsingContext) :
+ CodeProcessingException("Illegal import: $message", context)
+
+class InvalidProtoLogCallException(message: String, context: ParsingContext)
+ : CodeProcessingException("InvalidProtoLogCall: $message", context)
+
+class ParsingException(message: String, context: ParsingContext)
+ : CodeProcessingException(message, context)
class InvalidViewerConfigException(message: String) : Exception(message)
diff --git a/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt
index 0acbc90..b916f8f 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt
@@ -55,40 +55,40 @@
LogLevel.DEBUG, LogGroup("test2", true, true, "TAG")))
}
- @Test
- fun isWildcardStaticImported_true() {
+ @Test(expected = IllegalImportException::class)
+ fun checkWildcardStaticImported_true() {
val code = """package org.example.test;
import static org.example.Test.*;
"""
- assertTrue(CodeUtils.isWildcardStaticImported(
- StaticJavaParser.parse(code), "org.example.Test"))
+ CodeUtils.checkWildcardStaticImported(
+ StaticJavaParser.parse(code), "org.example.Test", "")
}
@Test
- fun isWildcardStaticImported_notStatic() {
+ fun checkWildcardStaticImported_notStatic() {
val code = """package org.example.test;
import org.example.Test.*;
"""
- assertFalse(CodeUtils.isWildcardStaticImported(
- StaticJavaParser.parse(code), "org.example.Test"))
+ CodeUtils.checkWildcardStaticImported(
+ StaticJavaParser.parse(code), "org.example.Test", "")
}
@Test
- fun isWildcardStaticImported_differentClass() {
+ fun checkWildcardStaticImported_differentClass() {
val code = """package org.example.test;
import static org.example.Test2.*;
"""
- assertFalse(CodeUtils.isWildcardStaticImported(
- StaticJavaParser.parse(code), "org.example.Test"))
+ CodeUtils.checkWildcardStaticImported(
+ StaticJavaParser.parse(code), "org.example.Test", "")
}
@Test
- fun isWildcardStaticImported_notWildcard() {
+ fun checkWildcardStaticImported_notWildcard() {
val code = """package org.example.test;
import org.example.Test.test;
"""
- assertFalse(CodeUtils.isWildcardStaticImported(
- StaticJavaParser.parse(code), "org.example.Test"))
+ CodeUtils.checkWildcardStaticImported(
+ StaticJavaParser.parse(code), "org.example.Test", "")
}
@Test
@@ -156,7 +156,7 @@
@Test
fun concatMultilineString_single() {
val str = StringLiteralExpr("test")
- val out = CodeUtils.concatMultilineString(str)
+ val out = CodeUtils.concatMultilineString(str, ParsingContext())
assertEquals("test", out)
}
@@ -166,7 +166,7 @@
"test" + "abc"
"""
val code = StaticJavaParser.parseExpression<BinaryExpr>(str)
- val out = CodeUtils.concatMultilineString(code)
+ val out = CodeUtils.concatMultilineString(code, ParsingContext())
assertEquals("testabc", out)
}
@@ -176,7 +176,7 @@
"test" + "abc" + "1234" + "test"
"""
val code = StaticJavaParser.parseExpression<BinaryExpr>(str)
- val out = CodeUtils.concatMultilineString(code)
+ val out = CodeUtils.concatMultilineString(code, ParsingContext())
assertEquals("testabc1234test", out)
}
}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt
index d20ce7e..97f67a0 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt
@@ -66,7 +66,7 @@
"""
groupMap["TEST"] = LogGroup("TEST", true, false, "WindowManager")
groupMap["ERROR"] = LogGroup("ERROR", true, true, "WindowManagerERROR")
- visitor.process(StaticJavaParser.parse(code), processor)
+ visitor.process(StaticJavaParser.parse(code), processor, "")
assertEquals(2, calls.size)
var c = calls[0]
assertEquals("test %b", c.messageString)
@@ -93,7 +93,7 @@
}
"""
groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
- visitor.process(StaticJavaParser.parse(code), processor)
+ visitor.process(StaticJavaParser.parse(code), processor, "")
checkCalls()
}
@@ -112,7 +112,7 @@
}
"""
groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
- visitor.process(StaticJavaParser.parse(code), processor)
+ visitor.process(StaticJavaParser.parse(code), processor, "")
checkCalls()
}
@@ -130,7 +130,7 @@
}
"""
groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
- visitor.process(StaticJavaParser.parse(code), processor)
+ visitor.process(StaticJavaParser.parse(code), processor, "")
}
@Test
@@ -147,7 +147,7 @@
}
"""
groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
- visitor.process(StaticJavaParser.parse(code), processor)
+ visitor.process(StaticJavaParser.parse(code), processor, "")
assertEquals(0, calls.size)
}
@@ -162,7 +162,7 @@
}
}
"""
- visitor.process(StaticJavaParser.parse(code), processor)
+ visitor.process(StaticJavaParser.parse(code), processor, "")
}
@Test(expected = InvalidProtoLogCallException::class)
@@ -176,7 +176,7 @@
}
}
"""
- visitor.process(StaticJavaParser.parse(code), processor)
+ visitor.process(StaticJavaParser.parse(code), processor, "")
}
@Test(expected = InvalidProtoLogCallException::class)
@@ -190,7 +190,7 @@
}
}
"""
- visitor.process(StaticJavaParser.parse(code), processor)
+ visitor.process(StaticJavaParser.parse(code), processor, "")
}
@Test(expected = InvalidProtoLogCallException::class)
@@ -204,7 +204,7 @@
}
}
"""
- visitor.process(StaticJavaParser.parse(code), processor)
+ visitor.process(StaticJavaParser.parse(code), processor, "")
}
@Test
@@ -220,7 +220,7 @@
}
"""
groupMap["TEST"] = LogGroup("TEST", false, true, "WindowManager")
- visitor.process(StaticJavaParser.parse(code), processor)
+ visitor.process(StaticJavaParser.parse(code), processor, "")
checkCalls()
}
}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
index 18504b6..e746300 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -175,7 +175,8 @@
var code = StaticJavaParser.parse(TEST_CODE)
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
@@ -212,7 +213,8 @@
var code = StaticJavaParser.parse(TEST_CODE_MULTICALLS)
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
val calls = code.findAll(MethodCallExpr::class.java)
@@ -254,7 +256,8 @@
var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
@@ -292,7 +295,8 @@
var code = StaticJavaParser.parse(TEST_CODE_NO_PARAMS)
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test",
@@ -326,7 +330,8 @@
var code = StaticJavaParser.parse(TEST_CODE)
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
@@ -363,7 +368,8 @@
var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
@@ -402,7 +408,8 @@
var code = StaticJavaParser.parse(TEST_CODE)
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
@@ -426,7 +433,8 @@
var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt
index d3f8c76..2b6abcd 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt
@@ -50,7 +50,8 @@
@Test
fun processClass() {
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
@@ -78,7 +79,8 @@
@Test
fun processClass_nonUnique() {
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
@@ -102,7 +104,8 @@
@Test
fun processClass_disabled() {
Mockito.`when`(processor.process(any(CompilationUnit::class.java),
- any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+ any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+ .thenAnswer { invocation ->
val visitor = invocation.arguments[1] as ProtoLogCallVisitor
visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,