Merge "Ignore Flaky test until the final solution available"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 9b290c6..2fd2e33 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -12,6 +12,8 @@
services/incremental/
[Hook Scripts]
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+
strings_lint_hook = ${REPO_ROOT}/frameworks/base/tools/stringslint/stringslint_sha.sh ${PREUPLOAD_COMMIT}
hidden_api_txt_checksorted_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 2312635..42725c5 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -59,9 +59,9 @@
*
* <p class="caution"><strong>Note:</strong> Beginning with API 30
* ({@link android.os.Build.VERSION_CODES#R}), JobScheduler will throttle runaway applications.
- * Calling {@link #schedule(JobInfo)} and other such methods with very high frequency is indicative
- * of an app bug and so, to make sure the system doesn't get overwhelmed, JobScheduler will begin
- * to throttle apps that show buggy behavior, regardless of target SDK version.
+ * Calling {@link #schedule(JobInfo)} and other such methods with very high frequency can have a
+ * high cost and so, to make sure the system doesn't get overwhelmed, JobScheduler will begin
+ * to throttle apps, regardless of target SDK version.
*/
@SystemService(Context.JOB_SCHEDULER_SERVICE)
public abstract class JobScheduler {
@@ -74,9 +74,16 @@
public @interface Result {}
/**
- * Returned from {@link #schedule(JobInfo)} when an invalid parameter was supplied. This can occur
- * if the run-time for your job is too short, or perhaps the system can't resolve the
- * requisite {@link JobService} in your package.
+ * Returned from {@link #schedule(JobInfo)} if a job wasn't scheduled successfully. Scheduling
+ * can fail for a variety of reasons, including, but not limited to:
+ * <ul>
+ * <li>an invalid parameter was supplied (eg. the run-time for your job is too short, or the
+ * system can't resolve the requisite {@link JobService} in your package)</li>
+ * <li>the app has too many jobs scheduled</li>
+ * <li>the app has tried to schedule too many jobs in a short amount of time</li>
+ * </ul>
+ * Attempting to schedule the job again immediately after receiving this result will not
+ * guarantee a successful schedule.
*/
public static final int RESULT_FAILURE = 0;
/**
@@ -89,6 +96,11 @@
* ID with the new information in the {@link JobInfo}. If a job with the given ID is currently
* running, it will be stopped.
*
+ * <p class="caution"><strong>Note:</strong> Scheduling a job can have a high cost, even if it's
+ * rescheduling the same job and the job didn't execute, especially on platform versions before
+ * version {@link android.os.Build.VERSION_CODES#Q}. As such, the system may throttle calls to
+ * this API if calls are made too frequently in a short amount of time.
+ *
* @param job The job you wish scheduled. See
* {@link android.app.job.JobInfo.Builder JobInfo.Builder} for more detail on the sorts of jobs
* you can schedule.
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index 47267df..8aa88c2 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -19,4 +19,8 @@
// Rename classes shared with the framework
jarjar_rules: "jarjar-rules.txt",
+
+ required: [
+ "libalarm_jni",
+ ],
}
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 3e43dcb..5ca3b33 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -3831,6 +3831,7 @@
}
void init() {
+ System.loadLibrary("alarm_jni");
mNativeData = AlarmManagerService.init();
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index ec9bdad..68b22c0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -254,6 +254,18 @@
private final CountQuotaTracker mQuotaTracker;
private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
+ private static final String QUOTA_TRACKER_SCHEDULE_LOGGED =
+ ".schedulePersisted out-of-quota logged";
+ private static final Category QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED = new Category(
+ ".schedulePersisted()");
+ private static final Category QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED = new Category(
+ ".schedulePersisted out-of-quota logged");
+ private static final Categorizer QUOTA_CATEGORIZER = (userId, packageName, tag) -> {
+ if (QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG.equals(tag)) {
+ return QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED;
+ }
+ return QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED;
+ };
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
@@ -270,6 +282,7 @@
ActivityManagerInternal mActivityManagerInternal;
IBatteryStats mBatteryStats;
DeviceIdleInternal mLocalDeviceIdleController;
+ @VisibleForTesting
AppStateTracker mAppStateTracker;
final UsageStatsManagerInternal mUsageStats;
private final AppStandbyInternal mAppStandbyInternal;
@@ -342,10 +355,7 @@
final StateController sc = mControllers.get(controller);
sc.onConstantsUpdatedLocked();
}
- mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS);
- mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
- mConstants.API_QUOTA_SCHEDULE_COUNT,
- mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
+ updateQuotaTracker();
} catch (IllegalArgumentException e) {
// Failed to parse the settings string, log this and move on
// with defaults.
@@ -355,6 +365,14 @@
}
}
+ @VisibleForTesting
+ void updateQuotaTracker() {
+ mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS);
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED,
+ mConstants.API_QUOTA_SCHEDULE_COUNT,
+ mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
+ }
+
static class MaxJobCounts {
private final KeyValueListParser.IntValue mTotal;
private final KeyValueListParser.IntValue mMaxBg;
@@ -507,6 +525,8 @@
private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms";
private static final String KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION =
"aq_schedule_throw_exception";
+ private static final String KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
+ "aq_schedule_return_failure";
private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
@@ -520,6 +540,7 @@
private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true;
+ private static final boolean DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
/**
* Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early.
@@ -623,6 +644,11 @@
*/
public boolean API_QUOTA_SCHEDULE_THROW_EXCEPTION =
DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION;
+ /**
+ * Whether or not to return a failure result when an app hits its schedule quota limit.
+ */
+ public boolean API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
+ DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -678,6 +704,9 @@
API_QUOTA_SCHEDULE_THROW_EXCEPTION = mParser.getBoolean(
KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION,
DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION);
+ API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = mParser.getBoolean(
+ KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT,
+ DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT);
}
void dump(IndentingPrintWriter pw) {
@@ -712,6 +741,8 @@
pw.print(KEY_API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS).println();
pw.print(KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION,
API_QUOTA_SCHEDULE_THROW_EXCEPTION).println();
+ pw.print(KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT,
+ API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT).println();
pw.decreaseIndent();
}
@@ -740,6 +771,8 @@
proto.write(ConstantsProto.API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS);
proto.write(ConstantsProto.API_QUOTA_SCHEDULE_THROW_EXCEPTION,
API_QUOTA_SCHEDULE_THROW_EXCEPTION);
+ proto.write(ConstantsProto.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT,
+ API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT);
}
}
@@ -973,12 +1006,17 @@
public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
int userId, String tag) {
- if (job.isPersisted()) {
- // Only limit schedule calls for persisted jobs.
+ final String servicePkg = job.getService().getPackageName();
+ if (job.isPersisted() && (packageName == null || packageName.equals(servicePkg))) {
+ // Only limit schedule calls for persisted jobs scheduled by the app itself.
final String pkg =
packageName == null ? job.getService().getPackageName() : packageName;
if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) {
- Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times");
+ if (mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_LOGGED)) {
+ // Don't log too frequently
+ Slog.wtf(TAG, userId + "-" + pkg + " has called schedule() too many times");
+ mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_LOGGED);
+ }
mAppStandbyInternal.restrictApp(
pkg, userId, UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION) {
@@ -1004,13 +1042,17 @@
// Only throw the exception for debuggable apps.
throw new LimitExceededException(
"schedule()/enqueue() called more than "
- + mQuotaTracker.getLimit(Category.SINGLE_CATEGORY)
+ + mQuotaTracker.getLimit(
+ QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED)
+ " times in the past "
- + mQuotaTracker.getWindowSizeMs(Category.SINGLE_CATEGORY)
- + "ms");
+ + mQuotaTracker.getWindowSizeMs(
+ QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED)
+ + "ms. See the documentation for more information.");
}
}
- return JobScheduler.RESULT_FAILURE;
+ if (mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT) {
+ return JobScheduler.RESULT_FAILURE;
+ }
}
mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
}
@@ -1371,10 +1413,12 @@
// Set up the app standby bucketing tracker
mStandbyTracker = new StandbyTracker();
mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
- mQuotaTracker = new CountQuotaTracker(context, Categorizer.SINGLE_CATEGORIZER);
- mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
+ mQuotaTracker = new CountQuotaTracker(context, QUOTA_CATEGORIZER);
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED,
mConstants.API_QUOTA_SCHEDULE_COUNT,
mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
+ // Log at most once per minute.
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED, 1, 60_000);
mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
mAppStandbyInternal.addListener(mStandbyTracker);
diff --git a/apex/jobscheduler/service/jni/Android.bp b/apex/jobscheduler/service/jni/Android.bp
new file mode 100644
index 0000000..c502867
--- /dev/null
+++ b/apex/jobscheduler/service/jni/Android.bp
@@ -0,0 +1,41 @@
+cc_library_shared {
+ name: "libalarm_jni",
+
+ cpp_std: "c++2a",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ ],
+
+ srcs: [
+ "com_android_server_alarm_AlarmManagerService.cpp",
+ "onload.cpp",
+ ],
+
+ shared_libs: [
+ "libnativehelper",
+ "liblog",
+ "libbase",
+ ],
+
+ product_variables: {
+ arc: {
+ exclude_srcs: [
+ "com_android_server_alarm_AlarmManagerService.cpp",
+ ],
+ srcs: [
+ ":arctimersrcs",
+ ],
+ }
+ }
+
+}
+
+filegroup {
+ name: "lib_alarmManagerService_native",
+ srcs: [
+ "com_android_server_alarm_AlarmManagerService.cpp",
+ ],
+}
diff --git a/services/core/jni/com_android_server_alarm_AlarmManagerService.cpp b/apex/jobscheduler/service/jni/com_android_server_alarm_AlarmManagerService.cpp
similarity index 100%
rename from services/core/jni/com_android_server_alarm_AlarmManagerService.cpp
rename to apex/jobscheduler/service/jni/com_android_server_alarm_AlarmManagerService.cpp
diff --git a/apex/jobscheduler/service/jni/onload.cpp b/apex/jobscheduler/service/jni/onload.cpp
new file mode 100644
index 0000000..f40413f
--- /dev/null
+++ b/apex/jobscheduler/service/jni/onload.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+namespace android {
+int register_android_server_alarm_AlarmManagerService(JNIEnv* env);
+};
+
+using namespace android;
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
+{
+ JNIEnv* env = NULL;
+ jint result = -1;
+
+ if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ ALOGE("GetEnv failed!");
+ return result;
+ }
+ ALOG_ASSERT(env, "Could not retrieve the env!");
+
+ register_android_server_alarm_AlarmManagerService(env);
+ return JNI_VERSION_1_4;
+}
diff --git a/api/current.txt b/api/current.txt
index c754f10..651cda5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -47044,7 +47044,7 @@
public abstract class CellLocation {
ctor public CellLocation();
method public static android.telephony.CellLocation getEmpty();
- method public static void requestLocationUpdate();
+ method @Deprecated public static void requestLocationUpdate();
}
public abstract class CellSignalStrength {
@@ -49106,6 +49106,7 @@
}
public static class MmTelFeature.MmTelCapabilities {
+ method public final boolean isCapable(int);
field public static final int CAPABILITY_TYPE_SMS = 8; // 0x8
field public static final int CAPABILITY_TYPE_UT = 4; // 0x4
field public static final int CAPABILITY_TYPE_VIDEO = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index 6a044508..fa45430 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -12250,7 +12250,6 @@
ctor @Deprecated public MmTelFeature.MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities);
ctor public MmTelFeature.MmTelCapabilities(int);
method public final void addCapabilities(int);
- method public final boolean isCapable(int);
method public final void removeCapabilities(int);
}
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 09544c1..3acc225 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -180,11 +180,6 @@
package android.telephony {
- public final class PreciseDataConnectionState implements android.os.Parcelable {
- method @Deprecated @Nullable public android.net.LinkProperties getDataConnectionLinkProperties();
- method @Deprecated public int getDataConnectionNetworkType();
- }
-
public class TelephonyManager {
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void answerRingingCall();
method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public boolean endCall();
diff --git a/api/test-current.txt b/api/test-current.txt
index ab07613..dc66265 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4697,7 +4697,6 @@
ctor @Deprecated public MmTelFeature.MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities);
ctor public MmTelFeature.MmTelCapabilities(int);
method public final void addCapabilities(int);
- method public final boolean isCapable(int);
method public final void removeCapabilities(int);
}
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 3449010..ab2f42b 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -18839,7 +18839,6 @@
HSPLandroid/telephony/PreciseDataConnectionState;-><init>(Landroid/os/Parcel;Landroid/telephony/PreciseDataConnectionState$1;)V
HPLandroid/telephony/PreciseDataConnectionState;->equals(Ljava/lang/Object;)Z
HPLandroid/telephony/PreciseDataConnectionState;->getDataConnectionApn()Ljava/lang/String;
-HPLandroid/telephony/PreciseDataConnectionState;->getDataConnectionLinkProperties()Landroid/net/LinkProperties;
HPLandroid/telephony/PreciseDataConnectionState;->getNetworkType()I
HPLandroid/telephony/PreciseDataConnectionState;->getState()I
HSPLandroid/telephony/PreciseDataConnectionState;->toString()Ljava/lang/String;
@@ -42425,10 +42424,6 @@
Landroid/hardware/camera2/impl/CameraMetadataNative;
Landroid/hardware/camera2/impl/GetCommand;
Landroid/hardware/camera2/impl/SetCommand;
-Landroid/hardware/camera2/legacy/LegacyCameraDevice;
-Landroid/hardware/camera2/legacy/LegacyExceptionUtils$BufferQueueAbandonedException;
-Landroid/hardware/camera2/legacy/LegacyMetadataMapper;
-Landroid/hardware/camera2/legacy/PerfMeasurement;
Landroid/hardware/camera2/marshal/MarshalHelpers;
Landroid/hardware/camera2/marshal/MarshalQueryable;
Landroid/hardware/camera2/marshal/MarshalRegistry$MarshalToken;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 0bff3aa..881cfa3 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -2297,10 +2297,6 @@
android.hardware.camera2.impl.CameraMetadataNative
android.hardware.camera2.impl.GetCommand
android.hardware.camera2.impl.SetCommand
-android.hardware.camera2.legacy.LegacyCameraDevice
-android.hardware.camera2.legacy.LegacyExceptionUtils$BufferQueueAbandonedException
-android.hardware.camera2.legacy.LegacyMetadataMapper
-android.hardware.camera2.legacy.PerfMeasurement
android.hardware.camera2.marshal.MarshalHelpers
android.hardware.camera2.marshal.MarshalQueryable
android.hardware.camera2.marshal.MarshalRegistry$MarshalToken
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index c498e92..d642f21 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -7542,6 +7542,7 @@
*
* @hide
*/
+ @SuppressWarnings("AndroidFrameworkClientSidePermissionCheck")
public int noteProxyOpNoThrow(int op, @Nullable String proxiedPackageName, int proxiedUid,
@Nullable String proxiedAttributionTag, @Nullable String message) {
int myUid = Process.myUid();
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 3bc043e..b1a62bf 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -16,6 +16,7 @@
package android.app.backup;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -34,6 +35,8 @@
import android.util.Log;
import android.util.Pair;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -195,6 +198,19 @@
@SystemApi
public static final int ERROR_TRANSPORT_INVALID = -2;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ OperationType.BACKUP,
+ OperationType.MIGRATION
+ })
+ public @interface OperationType {
+ // A regular backup / restore operation.
+ int BACKUP = 0;
+ // A full migration: all app data for non-system apps is eligible.
+ int MIGRATION = 1;
+ }
+
private Context mContext;
@UnsupportedAppUsage
private static IBackupManager sService;
@@ -736,7 +752,7 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.BACKUP)
public int requestBackup(String[] packages, BackupObserver observer) {
- return requestBackup(packages, observer, null, 0);
+ return requestBackup(packages, observer, null, 0, OperationType.BACKUP);
}
/**
@@ -761,6 +777,31 @@
@RequiresPermission(android.Manifest.permission.BACKUP)
public int requestBackup(String[] packages, BackupObserver observer,
BackupManagerMonitor monitor, int flags) {
+ return requestBackup(packages, observer, monitor, flags, OperationType.BACKUP);
+ }
+
+ /**
+ * Request an immediate backup, providing an observer to which results of the backup operation
+ * will be published. The Android backup system will decide for each package whether it will
+ * be full app data backup or key/value-pair-based backup.
+ *
+ * <p>If this method returns {@link BackupManager#SUCCESS}, the OS will attempt to backup all
+ * provided packages using the remote transport.
+ *
+ * @param packages List of package names to backup.
+ * @param observer The {@link BackupObserver} to receive callbacks during the backup
+ * operation. Could be {@code null}.
+ * @param monitor The {@link BackupManagerMonitorWrapper} to receive callbacks of important
+ * events during the backup operation. Could be {@code null}.
+ * @param flags {@link #FLAG_NON_INCREMENTAL_BACKUP}.
+ * @param operationType {@link OperationType}
+ * @return {@link BackupManager#SUCCESS} on success; nonzero on error.
+ * @throws IllegalArgumentException on null or empty {@code packages} param.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BACKUP)
+ public int requestBackup(String[] packages, BackupObserver observer,
+ BackupManagerMonitor monitor, int flags, @OperationType int operationType) {
checkServiceBinder();
if (sService != null) {
try {
@@ -770,7 +811,8 @@
BackupManagerMonitorWrapper monitorWrapper = monitor == null
? null
: new BackupManagerMonitorWrapper(monitor);
- return sService.requestBackup(packages, observerWrapper, monitorWrapper, flags);
+ return sService.requestBackup(packages, observerWrapper, monitorWrapper, flags,
+ operationType);
} catch (RemoteException e) {
Log.e(TAG, "requestBackup() couldn't connect");
}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 4940976..96b5dd5 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -678,7 +678,7 @@
* {@link android.app.backup.IBackupManager.requestBackupForUser} for the calling user id.
*/
int requestBackup(in String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor,
- int flags);
+ int flags, int operationType);
/**
* Cancel all running backups. After this call returns, no currently running backups will
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f533760..9c7dc96 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -8179,7 +8179,7 @@
private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
sPackageInfoCache =
new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
- 16, PermissionManager.CACHE_KEY_PACKAGE_INFO) {
+ 32, PermissionManager.CACHE_KEY_PACKAGE_INFO) {
@Override
protected PackageInfo recompute(PackageInfoQuery query) {
return getPackageInfoAsUserUncached(
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 25279b3..2159905 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -249,14 +249,10 @@
public static final int CAMERA_HAL_API_VERSION_1_0 = 0x100;
/**
- * A constant meaning the normal camera connect/open will be used.
+ * Camera HAL device API version 3.0
+ * @hide
*/
- private static final int CAMERA_HAL_API_VERSION_NORMAL_CONNECT = -2;
-
- /**
- * Used to indicate HAL version un-specified.
- */
- private static final int CAMERA_HAL_API_VERSION_UNSPECIFIED = -1;
+ public static final int CAMERA_HAL_API_VERSION_3_0 = 0x300;
/**
* Hardware face detection. It does not use much CPU.
@@ -427,7 +423,7 @@
* Creates a new Camera object to access a particular hardware camera with
* given hal API version. If the same camera is opened by other applications
* or the hal API version is not supported by this device, this will throw a
- * RuntimeException.
+ * RuntimeException. As of Android 12, HAL version 1 is no longer supported.
* <p>
* You must call {@link #release()} when you are done using the camera,
* otherwise it will remain locked and be unavailable to other applications.
@@ -463,49 +459,14 @@
*/
@UnsupportedAppUsage
public static Camera openLegacy(int cameraId, int halVersion) {
- if (halVersion < CAMERA_HAL_API_VERSION_1_0) {
- throw new IllegalArgumentException("Invalid HAL version " + halVersion);
+ if (halVersion < CAMERA_HAL_API_VERSION_3_0) {
+ throw new IllegalArgumentException("Unsupported HAL version " + halVersion);
}
- return new Camera(cameraId, halVersion);
+ return new Camera(cameraId);
}
- /**
- * Create a legacy camera object.
- *
- * @param cameraId The hardware camera to access, between 0 and
- * {@link #getNumberOfCameras()}-1.
- * @param halVersion The HAL API version this camera device to be opened as.
- */
- private Camera(int cameraId, int halVersion) {
- int err = cameraInitVersion(cameraId, halVersion);
- if (checkInitErrors(err)) {
- if (err == -EACCES) {
- throw new RuntimeException("Fail to connect to camera service");
- } else if (err == -ENODEV) {
- throw new RuntimeException("Camera initialization failed");
- } else if (err == -ENOSYS) {
- throw new RuntimeException("Camera initialization failed because some methods"
- + " are not implemented");
- } else if (err == -EOPNOTSUPP) {
- throw new RuntimeException("Camera initialization failed because the hal"
- + " version is not supported by this device");
- } else if (err == -EINVAL) {
- throw new RuntimeException("Camera initialization failed because the input"
- + " arugments are invalid");
- } else if (err == -EBUSY) {
- throw new RuntimeException("Camera initialization failed because the camera"
- + " device was already opened");
- } else if (err == -EUSERS) {
- throw new RuntimeException("Camera initialization failed because the max"
- + " number of camera devices were already opened");
- }
- // Should never hit this.
- throw new RuntimeException("Unknown camera error");
- }
- }
-
- private int cameraInitVersion(int cameraId, int halVersion) {
+ private int cameraInit(int cameraId) {
mShutterCallback = null;
mRawImageCallback = null;
mJpegCallback = null;
@@ -523,35 +484,13 @@
mEventHandler = null;
}
- return native_setup(new WeakReference<Camera>(this), cameraId, halVersion,
+ return native_setup(new WeakReference<Camera>(this), cameraId,
ActivityThread.currentOpPackageName());
}
- private int cameraInitNormal(int cameraId) {
- return cameraInitVersion(cameraId, CAMERA_HAL_API_VERSION_NORMAL_CONNECT);
- }
-
- /**
- * Connect to the camera service using #connectLegacy
- *
- * <p>
- * This acts the same as normal except that it will return
- * the detailed error code if open fails instead of
- * converting everything into {@code NO_INIT}.</p>
- *
- * <p>Intended to use by the camera2 shim only, do <i>not</i> use this for other code.</p>
- *
- * @return a detailed errno error code, or {@code NO_ERROR} on success
- *
- * @hide
- */
- public int cameraInitUnspecified(int cameraId) {
- return cameraInitVersion(cameraId, CAMERA_HAL_API_VERSION_UNSPECIFIED);
- }
-
/** used by Camera#open, Camera#open(int) */
Camera(int cameraId) {
- int err = cameraInitNormal(cameraId);
+ int err = cameraInit(cameraId);
if (checkInitErrors(err)) {
if (err == -EACCES) {
throw new RuntimeException("Fail to connect to camera service");
@@ -616,8 +555,7 @@
}
@UnsupportedAppUsage
- private native final int native_setup(Object camera_this, int cameraId, int halVersion,
- String packageName);
+ private native int native_setup(Object cameraThis, int cameraId, String packageName);
private native final void native_release();
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceLockoutResetCallback.aidl b/core/java/android/hardware/biometrics/IBiometricServiceLockoutResetCallback.aidl
index aa5ac03..754162c 100644
--- a/core/java/android/hardware/biometrics/IBiometricServiceLockoutResetCallback.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricServiceLockoutResetCallback.aidl
@@ -26,5 +26,5 @@
/**
* A wakelock will be held until the reciever calls back into {@param callback}
*/
- void onLockoutReset(IRemoteCallback callback);
+ void onLockoutReset(int sensorId, IRemoteCallback callback);
}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 7f834af..8469f5f 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -23,14 +23,11 @@
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.Context;
-import android.hardware.CameraInfo;
import android.hardware.CameraStatus;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceListener;
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.legacy.CameraDeviceUserShim;
-import android.hardware.camera2.legacy.LegacyMetadataMapper;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.utils.CameraIdAndSessionConfiguration;
import android.hardware.camera2.utils.ConcurrentCameraIdCombination;
@@ -405,10 +402,6 @@
throw new IllegalArgumentException("No cameras available on device");
}
synchronized (mLock) {
- /*
- * Get the camera characteristics from the camera service directly if it supports it,
- * otherwise get them from the legacy shim instead.
- */
ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
if (cameraService == null) {
throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
@@ -417,34 +410,18 @@
try {
Size displaySize = getDisplaySize();
- // First check isHiddenPhysicalCamera to avoid supportsCamera2ApiLocked throwing
- // exception in case cameraId is a hidden physical camera.
- if (!isHiddenPhysicalCamera(cameraId) && !supportsCamera2ApiLocked(cameraId)) {
- // Legacy backwards compatibility path; build static info from the camera
- // parameters
- int id = Integer.parseInt(cameraId);
-
- String parameters = cameraService.getLegacyParameters(id);
-
- CameraInfo info = cameraService.getCameraInfo(id);
-
- characteristics = LegacyMetadataMapper.createCharacteristics(parameters, info,
- id, displaySize);
- } else {
- // Normal path: Get the camera characteristics directly from the camera service
- CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId);
- try {
- info.setCameraId(Integer.parseInt(cameraId));
- } catch (NumberFormatException e) {
- // For external camera, reaching here is expected.
- Log.v(TAG, "Failed to parse camera Id " + cameraId + " to integer");
- }
- boolean hasConcurrentStreams =
- CameraManagerGlobal.get().cameraIdHasConcurrentStreamsLocked(cameraId);
- info.setHasMandatoryConcurrentStreams(hasConcurrentStreams);
- info.setDisplaySize(displaySize);
- characteristics = new CameraCharacteristics(info);
+ CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId);
+ try {
+ info.setCameraId(Integer.parseInt(cameraId));
+ } catch (NumberFormatException e) {
+ Log.v(TAG, "Failed to parse camera Id " + cameraId + " to integer");
}
+ boolean hasConcurrentStreams =
+ CameraManagerGlobal.get().cameraIdHasConcurrentStreamsLocked(cameraId);
+ info.setHasMandatoryConcurrentStreams(hasConcurrentStreams);
+ info.setDisplaySize(displaySize);
+ characteristics = new CameraCharacteristics(info);
+
} catch (ServiceSpecificException e) {
throwAsPublicException(e);
} catch (RemoteException e) {
@@ -500,30 +477,14 @@
ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
try {
- if (supportsCamera2ApiLocked(cameraId)) {
- // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices
- ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
- if (cameraService == null) {
- throw new ServiceSpecificException(
- ICameraService.ERROR_DISCONNECTED,
- "Camera service is currently unavailable");
- }
- cameraUser = cameraService.connectDevice(callbacks, cameraId,
- mContext.getOpPackageName(), mContext.getAttributionTag(), uid);
- } else {
- // Use legacy camera implementation for HAL1 devices
- int id;
- try {
- id = Integer.parseInt(cameraId);
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: "
- + cameraId);
- }
-
- Log.i(TAG, "Using legacy camera HAL.");
- cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id,
- getDisplaySize());
+ ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
+ if (cameraService == null) {
+ throw new ServiceSpecificException(
+ ICameraService.ERROR_DISCONNECTED,
+ "Camera service is currently unavailable");
}
+ cameraUser = cameraService.connectDevice(callbacks, cameraId,
+ mContext.getOpPackageName(), mContext.getAttributionTag(), uid);
} catch (ServiceSpecificException e) {
if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
throw new AssertionError("Should've gone down the shim path");
@@ -1021,44 +982,6 @@
}
/**
- * Queries the camera service if it supports the camera2 api directly, or needs a shim.
- *
- * @param cameraId a non-{@code null} camera identifier
- * @return {@code false} if the legacy shim needs to be used, {@code true} otherwise.
- */
- private boolean supportsCamera2ApiLocked(String cameraId) {
- return supportsCameraApiLocked(cameraId, API_VERSION_2);
- }
-
- /**
- * Queries the camera service if it supports a camera api directly, or needs a shim.
- *
- * @param cameraId a non-{@code null} camera identifier
- * @param apiVersion the version, i.e. {@code API_VERSION_1} or {@code API_VERSION_2}
- * @return {@code true} if connecting will work for that device version.
- */
- private boolean supportsCameraApiLocked(String cameraId, int apiVersion) {
- /*
- * Possible return values:
- * - NO_ERROR => CameraX API is supported
- * - CAMERA_DEPRECATED_HAL => CameraX API is *not* supported (thrown as an exception)
- * - Remote exception => If the camera service died
- *
- * Anything else is an unexpected error we don't want to recover from.
- */
- try {
- ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
- // If no camera service, no support
- if (cameraService == null) return false;
-
- return cameraService.supportsCameraApi(cameraId, apiVersion);
- } catch (RemoteException e) {
- // Camera service is now down, no support for any API level
- }
- return false;
- }
-
- /**
* Queries the camera service if a cameraId is a hidden physical camera that belongs to a
* logical camera device.
*
diff --git a/core/java/android/hardware/camera2/legacy/BurstHolder.java b/core/java/android/hardware/camera2/legacy/BurstHolder.java
deleted file mode 100644
index 23efe15..0000000
--- a/core/java/android/hardware/camera2/legacy/BurstHolder.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.hardware.camera2.CaptureRequest;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Immutable container for a burst of capture results.
- */
-public class BurstHolder {
- private static final String TAG = "BurstHolder";
- private final ArrayList<RequestHolder.Builder> mRequestBuilders;
- private final boolean mRepeating;
- private final int mRequestId;
-
- /**
- * Immutable container for a burst of capture results.
- *
- * @param requestId id of the burst request.
- * @param repeating true if this burst is repeating.
- * @param requests the array of {@link CaptureRequest}s for this burst.
- * @param jpegSurfaceIds a {@link Collection} of IDs for the surfaces that have jpeg outputs.
- */
- public BurstHolder(int requestId, boolean repeating, CaptureRequest[] requests,
- Collection<Long> jpegSurfaceIds) {
- mRequestBuilders = new ArrayList<>();
- int i = 0;
- for (CaptureRequest r : requests) {
- mRequestBuilders.add(new RequestHolder.Builder(requestId, /*subsequenceId*/i,
- /*request*/r, repeating, jpegSurfaceIds));
- ++i;
- }
- mRepeating = repeating;
- mRequestId = requestId;
- }
-
- /**
- * Get the id of this request.
- */
- public int getRequestId() {
- return mRequestId;
- }
-
- /**
- * Return true if this repeating.
- */
- public boolean isRepeating() {
- return mRepeating;
- }
-
- /**
- * Return the number of requests in this burst sequence.
- */
- public int getNumberOfRequests() {
- return mRequestBuilders.size();
- }
-
- /**
- * Create a list of {@link RequestHolder} objects encapsulating the requests in this burst.
- *
- * @param frameNumber the starting framenumber for this burst.
- * @return the list of {@link RequestHolder} objects.
- */
- public List<RequestHolder> produceRequestHolders(long frameNumber) {
- ArrayList<RequestHolder> holders = new ArrayList<RequestHolder>();
- int i = 0;
- for (RequestHolder.Builder b : mRequestBuilders) {
- holders.add(b.build(frameNumber + i));
- ++i;
- }
- return holders;
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
deleted file mode 100644
index 89ecd5f1c..0000000
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.hardware.camera2.impl.CameraDeviceImpl;
-import android.hardware.camera2.impl.CameraMetadataNative;
-import android.os.Handler;
-import android.util.Log;
-
-/**
- * Emulates a the state of a single Camera2 device.
- *
- * <p>
- * This class acts as the state machine for a camera device. Valid state transitions are given
- * in the table below:
- * </p>
- *
- * <ul>
- * <li>{@code UNCONFIGURED -> CONFIGURING}</li>
- * <li>{@code CONFIGURING -> IDLE}</li>
- * <li>{@code IDLE -> CONFIGURING}</li>
- * <li>{@code IDLE -> CAPTURING}</li>
- * <li>{@code IDLE -> IDLE}</li>
- * <li>{@code CAPTURING -> IDLE}</li>
- * <li>{@code ANY -> ERROR}</li>
- * </ul>
- */
-public class CameraDeviceState {
- private static final String TAG = "CameraDeviceState";
- private static final boolean DEBUG = false;
-
- private static final int STATE_ERROR = 0;
- private static final int STATE_UNCONFIGURED = 1;
- private static final int STATE_CONFIGURING = 2;
- private static final int STATE_IDLE = 3;
- private static final int STATE_CAPTURING = 4;
-
- private static final String[] sStateNames = { "ERROR", "UNCONFIGURED", "CONFIGURING", "IDLE",
- "CAPTURING"};
-
- private int mCurrentState = STATE_UNCONFIGURED;
- private int mCurrentError = NO_CAPTURE_ERROR;
-
- private RequestHolder mCurrentRequest = null;
-
- private Handler mCurrentHandler = null;
- private CameraDeviceStateListener mCurrentListener = null;
-
- /**
- * Error code used by {@link #setCaptureStart} and {@link #setCaptureResult} to indicate that no
- * error has occurred.
- */
- public static final int NO_CAPTURE_ERROR = -1;
-
- /**
- * CameraDeviceStateListener callbacks to be called after state transitions.
- */
- public interface CameraDeviceStateListener {
- void onError(int errorCode, Object errorArg, RequestHolder holder);
- void onConfiguring();
- void onIdle();
- void onBusy();
- void onCaptureStarted(RequestHolder holder, long timestamp);
- void onCaptureResult(CameraMetadataNative result, RequestHolder holder);
- void onRequestQueueEmpty();
- void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId);
- }
-
- /**
- * Transition to the {@code ERROR} state.
- *
- * <p>
- * The device cannot exit the {@code ERROR} state. If the device was not already in the
- * {@code ERROR} state, {@link CameraDeviceStateListener#onError(int, RequestHolder)} will be
- * called.
- * </p>
- *
- * @param error the error to set. Should be one of the error codes defined in
- * {@link CameraDeviceImpl.CameraDeviceCallbacks}.
- */
- public synchronized void setError(int error) {
- mCurrentError = error;
- doStateTransition(STATE_ERROR);
- }
-
- /**
- * Transition to the {@code CONFIGURING} state, or {@code ERROR} if in an invalid state.
- *
- * <p>
- * If the device was not already in the {@code CONFIGURING} state,
- * {@link CameraDeviceStateListener#onConfiguring()} will be called.
- * </p>
- *
- * @return {@code false} if an error has occurred.
- */
- public synchronized boolean setConfiguring() {
- doStateTransition(STATE_CONFIGURING);
- return mCurrentError == NO_CAPTURE_ERROR;
- }
-
- /**
- * Transition to the {@code IDLE} state, or {@code ERROR} if in an invalid state.
- *
- * <p>
- * If the device was not already in the {@code IDLE} state,
- * {@link CameraDeviceStateListener#onIdle()} will be called.
- * </p>
- *
- * @return {@code false} if an error has occurred.
- */
- public synchronized boolean setIdle() {
- doStateTransition(STATE_IDLE);
- return mCurrentError == NO_CAPTURE_ERROR;
- }
-
- /**
- * Transition to the {@code CAPTURING} state, or {@code ERROR} if in an invalid state.
- *
- * <p>
- * If the device was not already in the {@code CAPTURING} state,
- * {@link CameraDeviceStateListener#onCaptureStarted(RequestHolder)} will be called.
- * </p>
- *
- * @param request A {@link RequestHolder} containing the request for the current capture.
- * @param timestamp The timestamp of the capture start in nanoseconds.
- * @param captureError Report a recoverable error for a single request using a valid
- * error code for {@code ICameraDeviceCallbacks}, or
- * {@link #NO_CAPTURE_ERROR}
- * @return {@code false} if an error has occurred.
- */
- public synchronized boolean setCaptureStart(final RequestHolder request, long timestamp,
- int captureError) {
- mCurrentRequest = request;
- doStateTransition(STATE_CAPTURING, timestamp, captureError);
- return mCurrentError == NO_CAPTURE_ERROR;
- }
-
- /**
- * Set the result for a capture.
- *
- * <p>
- * If the device was in the {@code CAPTURING} state,
- * {@link CameraDeviceStateListener#onCaptureResult(CameraMetadataNative, RequestHolder)} will
- * be called with the given result, otherwise this will result in the device transitioning to
- * the {@code ERROR} state,
- * </p>
- *
- * @param request The {@link RequestHolder} request that created this result.
- * @param result The {@link CameraMetadataNative} result to set.
- * @param captureError Report a recoverable error for a single buffer or result using a valid
- * error code for {@code ICameraDeviceCallbacks}, or
- * {@link #NO_CAPTURE_ERROR}.
- * @param captureErrorArg An argument for some error captureError codes.
- * @return {@code false} if an error has occurred.
- */
- public synchronized boolean setCaptureResult(final RequestHolder request,
- final CameraMetadataNative result,
- final int captureError, final Object captureErrorArg) {
- if (mCurrentState != STATE_CAPTURING) {
- Log.e(TAG, "Cannot receive result while in state: " + mCurrentState);
- mCurrentError = CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE;
- doStateTransition(STATE_ERROR);
- return mCurrentError == NO_CAPTURE_ERROR;
- }
-
- if (mCurrentHandler != null && mCurrentListener != null) {
- if (captureError != NO_CAPTURE_ERROR) {
- mCurrentHandler.post(new Runnable() {
- @Override
- public void run() {
- mCurrentListener.onError(captureError, captureErrorArg, request);
- }
- });
- } else {
- mCurrentHandler.post(new Runnable() {
- @Override
- public void run() {
- mCurrentListener.onCaptureResult(result, request);
- }
- });
- }
- }
- return mCurrentError == NO_CAPTURE_ERROR;
- }
-
- public synchronized boolean setCaptureResult(final RequestHolder request,
- final CameraMetadataNative result) {
- return setCaptureResult(request, result, NO_CAPTURE_ERROR, /*errorArg*/null);
- }
-
- /**
- * Set repeating request error.
- *
- * <p>Repeating request has been stopped due to an error such as abandoned output surfaces.</p>
- *
- * @param lastFrameNumber Frame number of the last repeating request before it is stopped.
- * @param repeatingRequestId The ID of the repeating request being stopped
- */
- public synchronized void setRepeatingRequestError(final long lastFrameNumber,
- final int repeatingRequestId) {
- mCurrentHandler.post(new Runnable() {
- @Override
- public void run() {
- mCurrentListener.onRepeatingRequestError(lastFrameNumber, repeatingRequestId);
- }
- });
- }
-
- /**
- * Indicate that request queue (non-repeating) becomes empty.
- *
- * <p> Send notification that all non-repeating requests have been sent to camera device. </p>
- */
- public synchronized void setRequestQueueEmpty() {
- mCurrentHandler.post(new Runnable() {
- @Override
- public void run() {
- mCurrentListener.onRequestQueueEmpty();
- }
- });
- }
-
- /**
- * Set the listener for state transition callbacks.
- *
- * @param handler handler on which to call the callbacks.
- * @param listener the {@link CameraDeviceStateListener} callbacks to call.
- */
- public synchronized void setCameraDeviceCallbacks(Handler handler,
- CameraDeviceStateListener listener) {
- mCurrentHandler = handler;
- mCurrentListener = listener;
- }
-
- private void doStateTransition(int newState) {
- doStateTransition(newState, /*timestamp*/0, NO_CAPTURE_ERROR);
- }
-
- private void doStateTransition(int newState, final long timestamp, final int error) {
- if (newState != mCurrentState) {
- String stateName = "UNKNOWN";
- if (newState >= 0 && newState < sStateNames.length) {
- stateName = sStateNames[newState];
- }
- Log.i(TAG, "Legacy camera service transitioning to state " + stateName);
- }
-
- // If we transitioned into a non-IDLE/non-ERROR state then mark the device as busy
- if(newState != STATE_ERROR && newState != STATE_IDLE) {
- if (mCurrentState != newState && mCurrentHandler != null &&
- mCurrentListener != null) {
- mCurrentHandler.post(new Runnable() {
- @Override
- public void run() {
- mCurrentListener.onBusy();
- }
- });
- }
- }
-
- switch(newState) {
- case STATE_ERROR:
- if (mCurrentState != STATE_ERROR && mCurrentHandler != null &&
- mCurrentListener != null) {
- mCurrentHandler.post(new Runnable() {
- @Override
- public void run() {
- mCurrentListener.onError(mCurrentError, /*errorArg*/null, mCurrentRequest);
- }
- });
- }
- mCurrentState = STATE_ERROR;
- break;
- case STATE_CONFIGURING:
- if (mCurrentState != STATE_UNCONFIGURED && mCurrentState != STATE_IDLE) {
- Log.e(TAG, "Cannot call configure while in state: " + mCurrentState);
- mCurrentError = CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE;
- doStateTransition(STATE_ERROR);
- break;
- }
- if (mCurrentState != STATE_CONFIGURING && mCurrentHandler != null &&
- mCurrentListener != null) {
- mCurrentHandler.post(new Runnable() {
- @Override
- public void run() {
- mCurrentListener.onConfiguring();
- }
- });
- }
- mCurrentState = STATE_CONFIGURING;
- break;
- case STATE_IDLE:
- if (mCurrentState == STATE_IDLE) {
- break;
- }
-
- if (mCurrentState != STATE_CONFIGURING && mCurrentState != STATE_CAPTURING) {
- Log.e(TAG, "Cannot call idle while in state: " + mCurrentState);
- mCurrentError = CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE;
- doStateTransition(STATE_ERROR);
- break;
- }
-
- if (mCurrentState != STATE_IDLE && mCurrentHandler != null &&
- mCurrentListener != null) {
- mCurrentHandler.post(new Runnable() {
- @Override
- public void run() {
- mCurrentListener.onIdle();
- }
- });
- }
- mCurrentState = STATE_IDLE;
- break;
- case STATE_CAPTURING:
- if (mCurrentState != STATE_IDLE && mCurrentState != STATE_CAPTURING) {
- Log.e(TAG, "Cannot call capture while in state: " + mCurrentState);
- mCurrentError = CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE;
- doStateTransition(STATE_ERROR);
- break;
- }
-
- if (mCurrentHandler != null && mCurrentListener != null) {
- if (error != NO_CAPTURE_ERROR) {
- mCurrentHandler.post(new Runnable() {
- @Override
- public void run() {
- mCurrentListener.onError(error, /*errorArg*/null, mCurrentRequest);
- }
- });
- } else {
- mCurrentHandler.post(new Runnable() {
- @Override
- public void run() {
- mCurrentListener.onCaptureStarted(mCurrentRequest, timestamp);
- }
- });
- }
- }
- mCurrentState = STATE_CAPTURING;
- break;
- default:
- throw new IllegalStateException("Transition to unknown state: " + newState);
- }
- }
-
-
-}
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
deleted file mode 100644
index cf8cab2..0000000
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ /dev/null
@@ -1,805 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.hardware.ICameraService;
-import android.hardware.Camera;
-import android.hardware.Camera.CameraInfo;
-import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.ICameraDeviceCallbacks;
-import android.hardware.camera2.ICameraDeviceUser;
-import android.hardware.camera2.ICameraOfflineSession;
-import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.impl.CaptureResultExtras;
-import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
-import android.hardware.camera2.params.OutputConfiguration;
-import android.hardware.camera2.params.SessionConfiguration;
-import android.hardware.camera2.utils.SubmitInfo;
-import android.os.ConditionVariable;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import android.util.Log;
-import android.util.Size;
-import android.util.SparseArray;
-import android.view.Surface;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static android.system.OsConstants.EACCES;
-import static android.system.OsConstants.ENODEV;
-
-/**
- * Compatibility implementation of the Camera2 API binder interface.
- *
- * <p>
- * This is intended to be called from the same process as client
- * {@link android.hardware.camera2.CameraDevice}, and wraps a
- * {@link android.hardware.camera2.legacy.LegacyCameraDevice} that emulates Camera2 service using
- * the Camera1 API.
- * </p>
- *
- * <p>
- * Keep up to date with ICameraDeviceUser.aidl.
- * </p>
- */
-@SuppressWarnings("deprecation")
-public class CameraDeviceUserShim implements ICameraDeviceUser {
- private static final String TAG = "CameraDeviceUserShim";
-
- private static final boolean DEBUG = false;
- private static final int OPEN_CAMERA_TIMEOUT_MS = 5000; // 5 sec (same as api1 cts timeout)
-
- private final LegacyCameraDevice mLegacyDevice;
-
- private final Object mConfigureLock = new Object();
- private int mSurfaceIdCounter;
- private boolean mConfiguring;
- private final SparseArray<Surface> mSurfaces;
- private final CameraCharacteristics mCameraCharacteristics;
- private final CameraLooper mCameraInit;
- private final CameraCallbackThread mCameraCallbacks;
-
-
- protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera,
- CameraCharacteristics characteristics, CameraLooper cameraInit,
- CameraCallbackThread cameraCallbacks) {
- mLegacyDevice = legacyCamera;
- mConfiguring = false;
- mSurfaces = new SparseArray<Surface>();
- mCameraCharacteristics = characteristics;
- mCameraInit = cameraInit;
- mCameraCallbacks = cameraCallbacks;
-
- mSurfaceIdCounter = 0;
- }
-
- private static int translateErrorsFromCamera1(int errorCode) {
- if (errorCode == -EACCES) {
- return ICameraService.ERROR_PERMISSION_DENIED;
- }
-
- return errorCode;
- }
-
- /**
- * Create a separate looper/thread for the camera to run on; open the camera.
- *
- * <p>Since the camera automatically latches on to the current thread's looper,
- * it's important that we have our own thread with our own looper to guarantee
- * that the camera callbacks get correctly posted to our own thread.</p>
- */
- private static class CameraLooper implements Runnable, AutoCloseable {
- private final int mCameraId;
- private Looper mLooper;
- private volatile int mInitErrors;
- private final Camera mCamera = Camera.openUninitialized();
- private final ConditionVariable mStartDone = new ConditionVariable();
- private final Thread mThread;
-
- /**
- * Spin up a new thread, immediately open the camera in the background.
- *
- * <p>Use {@link #waitForOpen} to block until the camera is finished opening.</p>
- *
- * @param cameraId numeric camera Id
- *
- * @see #waitForOpen
- */
- public CameraLooper(int cameraId) {
- mCameraId = cameraId;
-
- mThread = new Thread(this, "LegacyCameraLooper");
- mThread.start();
- }
-
- public Camera getCamera() {
- return mCamera;
- }
-
- @Override
- public void run() {
- // Set up a looper to be used by camera.
- Looper.prepare();
-
- // Save the looper so that we can terminate this thread
- // after we are done with it.
- mLooper = Looper.myLooper();
- mInitErrors = mCamera.cameraInitUnspecified(mCameraId);
- mStartDone.open();
- Looper.loop(); // Blocks forever until #close is called.
- }
-
- /**
- * Quit the looper safely; then join until the thread shuts down.
- */
- @Override
- public void close() {
- if (mLooper == null) {
- return;
- }
-
- mLooper.quitSafely();
- try {
- mThread.join();
- } catch (InterruptedException e) {
- throw new AssertionError(e);
- }
-
- mLooper = null;
- }
-
- /**
- * Block until the camera opens; then return its initialization error code (if any).
- *
- * @param timeoutMs timeout in milliseconds
- *
- * @return int error code
- *
- * @throws ServiceSpecificException if the camera open times out with ({@code CAMERA_ERROR})
- */
- public int waitForOpen(int timeoutMs) {
- // Block until the camera is open asynchronously
- if (!mStartDone.block(timeoutMs)) {
- Log.e(TAG, "waitForOpen - Camera failed to open after timeout of "
- + OPEN_CAMERA_TIMEOUT_MS + " ms");
- try {
- mCamera.release();
- } catch (RuntimeException e) {
- Log.e(TAG, "connectBinderShim - Failed to release camera after timeout ", e);
- }
-
- throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION);
- }
-
- return mInitErrors;
- }
- }
-
- /**
- * A thread to process callbacks to send back to the camera client.
- *
- * <p>This effectively emulates one-way binder semantics when in the same process as the
- * callee.</p>
- */
- private static class CameraCallbackThread implements ICameraDeviceCallbacks {
- private static final int CAMERA_ERROR = 0;
- private static final int CAMERA_IDLE = 1;
- private static final int CAPTURE_STARTED = 2;
- private static final int RESULT_RECEIVED = 3;
- private static final int PREPARED = 4;
- private static final int REPEATING_REQUEST_ERROR = 5;
- private static final int REQUEST_QUEUE_EMPTY = 6;
-
- private final HandlerThread mHandlerThread;
- private Handler mHandler;
-
- private final ICameraDeviceCallbacks mCallbacks;
-
- public CameraCallbackThread(ICameraDeviceCallbacks callbacks) {
- mCallbacks = callbacks;
-
- mHandlerThread = new HandlerThread("LegacyCameraCallback");
- mHandlerThread.start();
- }
-
- public void close() {
- mHandlerThread.quitSafely();
- }
-
- @Override
- public void onDeviceError(final int errorCode, final CaptureResultExtras resultExtras) {
- Message msg = getHandler().obtainMessage(CAMERA_ERROR,
- /*arg1*/ errorCode, /*arg2*/ 0,
- /*obj*/ resultExtras);
- getHandler().sendMessage(msg);
- }
-
- @Override
- public void onDeviceIdle() {
- Message msg = getHandler().obtainMessage(CAMERA_IDLE);
- getHandler().sendMessage(msg);
- }
-
- @Override
- public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
- Message msg = getHandler().obtainMessage(CAPTURE_STARTED,
- /*arg1*/ (int) (timestamp & 0xFFFFFFFFL),
- /*arg2*/ (int) ( (timestamp >> 32) & 0xFFFFFFFFL),
- /*obj*/ resultExtras);
- getHandler().sendMessage(msg);
- }
-
- @Override
- public void onResultReceived(final CameraMetadataNative result,
- final CaptureResultExtras resultExtras,
- PhysicalCaptureResultInfo physicalResults[]) {
- Object[] resultArray = new Object[] { result, resultExtras };
- Message msg = getHandler().obtainMessage(RESULT_RECEIVED,
- /*obj*/ resultArray);
- getHandler().sendMessage(msg);
- }
-
- @Override
- public void onPrepared(int streamId) {
- Message msg = getHandler().obtainMessage(PREPARED,
- /*arg1*/ streamId, /*arg2*/ 0);
- getHandler().sendMessage(msg);
- }
-
- @Override
- public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) {
- Object[] objArray = new Object[] { lastFrameNumber, repeatingRequestId };
- Message msg = getHandler().obtainMessage(REPEATING_REQUEST_ERROR,
- /*obj*/ objArray);
- getHandler().sendMessage(msg);
- }
-
- @Override
- public void onRequestQueueEmpty() {
- Message msg = getHandler().obtainMessage(REQUEST_QUEUE_EMPTY,
- /* arg1 */ 0, /* arg2 */ 0);
- getHandler().sendMessage(msg);
- }
-
- @Override
- public IBinder asBinder() {
- // This is solely intended to be used for in-process binding.
- return null;
- }
-
- private Handler getHandler() {
- if (mHandler == null) {
- mHandler = new CallbackHandler(mHandlerThread.getLooper());
- }
- return mHandler;
- }
-
- private class CallbackHandler extends Handler {
- public CallbackHandler(Looper l) {
- super(l);
- }
-
- @Override
- public void handleMessage(Message msg) {
- try {
- switch (msg.what) {
- case CAMERA_ERROR: {
- int errorCode = msg.arg1;
- CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj;
- mCallbacks.onDeviceError(errorCode, resultExtras);
- break;
- }
- case CAMERA_IDLE:
- mCallbacks.onDeviceIdle();
- break;
- case CAPTURE_STARTED: {
- long timestamp = msg.arg2 & 0xFFFFFFFFL;
- timestamp = (timestamp << 32) | (msg.arg1 & 0xFFFFFFFFL);
- CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj;
- mCallbacks.onCaptureStarted(resultExtras, timestamp);
- break;
- }
- case RESULT_RECEIVED: {
- Object[] resultArray = (Object[]) msg.obj;
- CameraMetadataNative result = (CameraMetadataNative) resultArray[0];
- CaptureResultExtras resultExtras = (CaptureResultExtras) resultArray[1];
- mCallbacks.onResultReceived(result, resultExtras,
- new PhysicalCaptureResultInfo[0]);
- break;
- }
- case PREPARED: {
- int streamId = msg.arg1;
- mCallbacks.onPrepared(streamId);
- break;
- }
- case REPEATING_REQUEST_ERROR: {
- Object[] objArray = (Object[]) msg.obj;
- long lastFrameNumber = (Long) objArray[0];
- int repeatingRequestId = (Integer) objArray[1];
- mCallbacks.onRepeatingRequestError(lastFrameNumber, repeatingRequestId);
- break;
- }
- case REQUEST_QUEUE_EMPTY: {
- mCallbacks.onRequestQueueEmpty();
- break;
- }
- default:
- throw new IllegalArgumentException(
- "Unknown callback message " + msg.what);
- }
- } catch (RemoteException e) {
- throw new IllegalStateException(
- "Received remote exception during camera callback " + msg.what, e);
- }
- }
- }
- }
-
- public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
- int cameraId, Size displaySize) {
- if (DEBUG) {
- Log.d(TAG, "Opening shim Camera device");
- }
-
- /*
- * Put the camera open on a separate thread with its own looper; otherwise
- * if the main thread is used then the callbacks might never get delivered
- * (e.g. in CTS which run its own default looper only after tests)
- */
-
- CameraLooper init = new CameraLooper(cameraId);
-
- CameraCallbackThread threadCallbacks = new CameraCallbackThread(callbacks);
-
- // TODO: Make this async instead of blocking
- int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS);
- Camera legacyCamera = init.getCamera();
-
- // Check errors old HAL initialization
- LegacyExceptionUtils.throwOnServiceError(initErrors);
-
- // Disable shutter sounds (this will work unconditionally) for api2 clients
- legacyCamera.disableShutterSound();
-
- CameraInfo info = new CameraInfo();
- Camera.getCameraInfo(cameraId, info);
-
- Camera.Parameters legacyParameters = null;
- try {
- legacyParameters = legacyCamera.getParameters();
- } catch (RuntimeException e) {
- throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION,
- "Unable to get initial parameters: " + e.getMessage());
- }
-
- CameraCharacteristics characteristics =
- LegacyMetadataMapper.createCharacteristics(legacyParameters, info, cameraId,
- displaySize);
- LegacyCameraDevice device = new LegacyCameraDevice(
- cameraId, legacyCamera, characteristics, threadCallbacks);
- return new CameraDeviceUserShim(cameraId, device, characteristics, init, threadCallbacks);
- }
-
- @Override
- public void disconnect() {
- if (DEBUG) {
- Log.d(TAG, "disconnect called.");
- }
-
- if (mLegacyDevice.isClosed()) {
- Log.w(TAG, "Cannot disconnect, device has already been closed.");
- }
-
- try {
- mLegacyDevice.close();
- } finally {
- mCameraInit.close();
- mCameraCallbacks.close();
- }
- }
-
- @Override
- public SubmitInfo submitRequest(CaptureRequest request, boolean streaming) {
- if (DEBUG) {
- Log.d(TAG, "submitRequest called.");
- }
- if (mLegacyDevice.isClosed()) {
- String err = "Cannot submit request, device has been closed.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
- }
-
- synchronized(mConfigureLock) {
- if (mConfiguring) {
- String err = "Cannot submit request, configuration change in progress.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
- }
- }
- return mLegacyDevice.submitRequest(request, streaming);
- }
-
- @Override
- public SubmitInfo submitRequestList(CaptureRequest[] request, boolean streaming) {
- if (DEBUG) {
- Log.d(TAG, "submitRequestList called.");
- }
- if (mLegacyDevice.isClosed()) {
- String err = "Cannot submit request list, device has been closed.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
- }
-
- synchronized(mConfigureLock) {
- if (mConfiguring) {
- String err = "Cannot submit request, configuration change in progress.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
- }
- }
- return mLegacyDevice.submitRequestList(request, streaming);
- }
-
- @Override
- public long cancelRequest(int requestId) {
- if (DEBUG) {
- Log.d(TAG, "cancelRequest called.");
- }
- if (mLegacyDevice.isClosed()) {
- String err = "Cannot cancel request, device has been closed.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
- }
-
- synchronized(mConfigureLock) {
- if (mConfiguring) {
- String err = "Cannot cancel request, configuration change in progress.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
- }
- }
- return mLegacyDevice.cancelRequest(requestId);
- }
-
- @Override
- public boolean isSessionConfigurationSupported(SessionConfiguration sessionConfig) {
- if (sessionConfig.getSessionType() != SessionConfiguration.SESSION_REGULAR) {
- Log.e(TAG, "Session type: " + sessionConfig.getSessionType() + " is different from " +
- " regular. Legacy devices support only regular session types!");
- return false;
- }
-
- if (sessionConfig.getInputConfiguration() != null) {
- Log.e(TAG, "Input configuration present, legacy devices do not support this feature!");
- return false;
- }
-
- List<OutputConfiguration> outputConfigs = sessionConfig.getOutputConfigurations();
- if (outputConfigs.isEmpty()) {
- Log.e(TAG, "Empty output configuration list!");
- return false;
- }
-
- SparseArray<Surface> surfaces = new SparseArray<Surface>(outputConfigs.size());
- int idx = 0;
- for (OutputConfiguration outputConfig : outputConfigs) {
- List<Surface> surfaceList = outputConfig.getSurfaces();
- if (surfaceList.isEmpty() || (surfaceList.size() > 1)) {
- Log.e(TAG, "Legacy devices do not support deferred or shared surfaces!");
- return false;
- }
-
- surfaces.put(idx++, outputConfig.getSurface());
- }
-
- int ret = mLegacyDevice.configureOutputs(surfaces, /*validateSurfacesOnly*/true);
-
- return ret == LegacyExceptionUtils.NO_ERROR;
- }
-
- @Override
- public void beginConfigure() {
- if (DEBUG) {
- Log.d(TAG, "beginConfigure called.");
- }
- if (mLegacyDevice.isClosed()) {
- String err = "Cannot begin configure, device has been closed.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
- }
-
- synchronized(mConfigureLock) {
- if (mConfiguring) {
- String err = "Cannot begin configure, configuration change already in progress.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
- }
- mConfiguring = true;
- }
- }
-
- @Override
- public int[] endConfigure(int operatingMode, CameraMetadataNative sessionParams) {
- if (DEBUG) {
- Log.d(TAG, "endConfigure called.");
- }
- if (mLegacyDevice.isClosed()) {
- String err = "Cannot end configure, device has been closed.";
- Log.e(TAG, err);
- synchronized(mConfigureLock) {
- mConfiguring = false;
- }
- throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
- }
-
- if (operatingMode != ICameraDeviceUser.NORMAL_MODE) {
- String err = "LEGACY devices do not support this operating mode";
- Log.e(TAG, err);
- synchronized(mConfigureLock) {
- mConfiguring = false;
- }
- throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
- }
-
- SparseArray<Surface> surfaces = null;
- synchronized(mConfigureLock) {
- if (!mConfiguring) {
- String err = "Cannot end configure, no configuration change in progress.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
- }
- if (mSurfaces != null) {
- surfaces = mSurfaces.clone();
- }
- mConfiguring = false;
- }
- mLegacyDevice.configureOutputs(surfaces);
-
- return new int[0]; // Offline mode is not supported
- }
-
- @Override
- public void deleteStream(int streamId) {
- if (DEBUG) {
- Log.d(TAG, "deleteStream called.");
- }
- if (mLegacyDevice.isClosed()) {
- String err = "Cannot delete stream, device has been closed.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
- }
-
- synchronized(mConfigureLock) {
- if (!mConfiguring) {
- String err = "Cannot delete stream, no configuration change in progress.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
- }
- int index = mSurfaces.indexOfKey(streamId);
- if (index < 0) {
- String err = "Cannot delete stream, stream id " + streamId + " doesn't exist.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
- }
- mSurfaces.removeAt(index);
- }
- }
-
- @Override
- public int createStream(OutputConfiguration outputConfiguration) {
- if (DEBUG) {
- Log.d(TAG, "createStream called.");
- }
- if (mLegacyDevice.isClosed()) {
- String err = "Cannot create stream, device has been closed.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
- }
-
- synchronized(mConfigureLock) {
- if (!mConfiguring) {
- String err = "Cannot create stream, beginConfigure hasn't been called yet.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
- }
- if (outputConfiguration.getRotation() != OutputConfiguration.ROTATION_0) {
- String err = "Cannot create stream, stream rotation is not supported.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
- }
- int id = ++mSurfaceIdCounter;
- mSurfaces.put(id, outputConfiguration.getSurface());
- return id;
- }
- }
-
- @Override
- public void finalizeOutputConfigurations(int steamId, OutputConfiguration config) {
- String err = "Finalizing output configuration is not supported on legacy devices";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
- }
-
- @Override
- public int createInputStream(int width, int height, int format) {
- String err = "Creating input stream is not supported on legacy devices";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
- }
-
- @Override
- public Surface getInputSurface() {
- String err = "Getting input surface is not supported on legacy devices";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
- }
-
- @Override
- public CameraMetadataNative createDefaultRequest(int templateId) {
- if (DEBUG) {
- Log.d(TAG, "createDefaultRequest called.");
- }
- if (mLegacyDevice.isClosed()) {
- String err = "Cannot create default request, device has been closed.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
- }
-
- CameraMetadataNative template;
- try {
- template =
- LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId);
- } catch (IllegalArgumentException e) {
- String err = "createDefaultRequest - invalid templateId specified";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
- }
-
- return template;
- }
-
- @Override
- public CameraMetadataNative getCameraInfo() {
- if (DEBUG) {
- Log.d(TAG, "getCameraInfo called.");
- }
- // TODO: implement getCameraInfo.
- Log.e(TAG, "getCameraInfo unimplemented.");
- return null;
- }
-
- @Override
- public void updateOutputConfiguration(int streamId, OutputConfiguration config) {
- // TODO: b/63912484 implement updateOutputConfiguration.
- }
-
- @Override
- public void waitUntilIdle() throws RemoteException {
- if (DEBUG) {
- Log.d(TAG, "waitUntilIdle called.");
- }
- if (mLegacyDevice.isClosed()) {
- String err = "Cannot wait until idle, device has been closed.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
- }
-
- synchronized(mConfigureLock) {
- if (mConfiguring) {
- String err = "Cannot wait until idle, configuration change in progress.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
- }
- }
- mLegacyDevice.waitUntilIdle();
- }
-
- @Override
- public long flush() {
- if (DEBUG) {
- Log.d(TAG, "flush called.");
- }
- if (mLegacyDevice.isClosed()) {
- String err = "Cannot flush, device has been closed.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
- }
-
- synchronized(mConfigureLock) {
- if (mConfiguring) {
- String err = "Cannot flush, configuration change in progress.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
- }
- }
- return mLegacyDevice.flush();
- }
-
- public void prepare(int streamId) {
- if (DEBUG) {
- Log.d(TAG, "prepare called.");
- }
- if (mLegacyDevice.isClosed()) {
- String err = "Cannot prepare stream, device has been closed.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
- }
-
- // LEGACY doesn't support actual prepare, just signal success right away
- mCameraCallbacks.onPrepared(streamId);
- }
-
- public void prepare2(int maxCount, int streamId) {
- // We don't support this in LEGACY mode.
- prepare(streamId);
- }
-
- public void tearDown(int streamId) {
- if (DEBUG) {
- Log.d(TAG, "tearDown called.");
- }
- if (mLegacyDevice.isClosed()) {
- String err = "Cannot tear down stream, device has been closed.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
- }
-
- // LEGACY doesn't support actual teardown, so just a no-op
- }
-
- @Override
- public void setCameraAudioRestriction(int mode) {
- if (mLegacyDevice.isClosed()) {
- String err = "Cannot set camera audio restriction, device has been closed.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
- }
-
- mLegacyDevice.setAudioRestriction(mode);
- }
-
- @Override
- public int getGlobalAudioRestriction() {
- if (mLegacyDevice.isClosed()) {
- String err = "Cannot set camera audio restriction, device has been closed.";
- Log.e(TAG, err);
- throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
- }
-
- return mLegacyDevice.getAudioRestriction();
- }
-
- @Override
- public ICameraOfflineSession switchToOffline(ICameraDeviceCallbacks cbs,
- int[] offlineOutputIds) {
- throw new UnsupportedOperationException("Legacy device does not support offline mode");
- }
-
- @Override
- public IBinder asBinder() {
- // This is solely intended to be used for in-process binding.
- return null;
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/CaptureCollector.java b/core/java/android/hardware/camera2/legacy/CaptureCollector.java
deleted file mode 100644
index 113927c..0000000
--- a/core/java/android/hardware/camera2/legacy/CaptureCollector.java
+++ /dev/null
@@ -1,673 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.hardware.camera2.impl.CameraDeviceImpl;
-import android.util.Log;
-import android.util.MutableLong;
-import android.util.Pair;
-import android.view.Surface;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.TreeSet;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Collect timestamps and state for each {@link CaptureRequest} as it passes through
- * the Legacy camera pipeline.
- */
-public class CaptureCollector {
- private static final String TAG = "CaptureCollector";
-
- private static final boolean DEBUG = false;
-
- private static final int FLAG_RECEIVED_JPEG = 1;
- private static final int FLAG_RECEIVED_JPEG_TS = 2;
- private static final int FLAG_RECEIVED_PREVIEW = 4;
- private static final int FLAG_RECEIVED_PREVIEW_TS = 8;
- private static final int FLAG_RECEIVED_ALL_JPEG = FLAG_RECEIVED_JPEG | FLAG_RECEIVED_JPEG_TS;
- private static final int FLAG_RECEIVED_ALL_PREVIEW = FLAG_RECEIVED_PREVIEW |
- FLAG_RECEIVED_PREVIEW_TS;
-
- private static final int MAX_JPEGS_IN_FLIGHT = 1;
-
- private class CaptureHolder implements Comparable<CaptureHolder>{
- private final RequestHolder mRequest;
- private final LegacyRequest mLegacy;
- public final boolean needsJpeg;
- public final boolean needsPreview;
-
- private long mTimestamp = 0;
- private int mReceivedFlags = 0;
- private boolean mHasStarted = false;
- private boolean mFailedJpeg = false;
- private boolean mFailedPreview = false;
- private boolean mCompleted = false;
- private boolean mPreviewCompleted = false;
-
- public CaptureHolder(RequestHolder request, LegacyRequest legacyHolder) {
- mRequest = request;
- mLegacy = legacyHolder;
- needsJpeg = request.hasJpegTargets();
- needsPreview = request.hasPreviewTargets();
- }
-
- public boolean isPreviewCompleted() {
- return (mReceivedFlags & FLAG_RECEIVED_ALL_PREVIEW) == FLAG_RECEIVED_ALL_PREVIEW;
- }
-
- public boolean isJpegCompleted() {
- return (mReceivedFlags & FLAG_RECEIVED_ALL_JPEG) == FLAG_RECEIVED_ALL_JPEG;
- }
-
- public boolean isCompleted() {
- return (needsJpeg == isJpegCompleted()) && (needsPreview == isPreviewCompleted());
- }
-
- public void tryComplete() {
- if (!mPreviewCompleted && needsPreview && isPreviewCompleted()) {
- CaptureCollector.this.onPreviewCompleted();
- mPreviewCompleted = true;
- }
-
- if (isCompleted() && !mCompleted) {
- if (mFailedPreview || mFailedJpeg) {
- if (!mHasStarted) {
- // Send a request error if the capture has not yet started.
- mRequest.failRequest();
- CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_REQUEST);
- } else {
- // Send buffer dropped errors for each pending buffer if the request has
- // started.
- for (Surface targetSurface : mRequest.getRequest().getTargets() ) {
- try {
- if (mRequest.jpegType(targetSurface)) {
- if (mFailedJpeg) {
- CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
- /*result*/null,
- CameraDeviceImpl.CameraDeviceCallbacks.
- ERROR_CAMERA_BUFFER,
- targetSurface);
- }
- } else {
- // preview buffer
- if (mFailedPreview) {
- CaptureCollector.this.mDeviceState.setCaptureResult(mRequest,
- /*result*/null,
- CameraDeviceImpl.CameraDeviceCallbacks.
- ERROR_CAMERA_BUFFER,
- targetSurface);
- }
- }
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.e(TAG, "Unexpected exception when querying Surface: " + e);
- }
- }
- }
- }
- CaptureCollector.this.onRequestCompleted(CaptureHolder.this);
- mCompleted = true;
- }
- }
-
- public void setJpegTimestamp(long timestamp) {
- if (DEBUG) {
- Log.d(TAG, "setJpegTimestamp - called for request " + mRequest.getRequestId());
- }
- if (!needsJpeg) {
- throw new IllegalStateException(
- "setJpegTimestamp called for capture with no jpeg targets.");
- }
- if (isCompleted()) {
- throw new IllegalStateException(
- "setJpegTimestamp called on already completed request.");
- }
-
- mReceivedFlags |= FLAG_RECEIVED_JPEG_TS;
-
- if (mTimestamp == 0) {
- mTimestamp = timestamp;
- }
-
- if (!mHasStarted) {
- mHasStarted = true;
- CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
- CameraDeviceState.NO_CAPTURE_ERROR);
- }
-
- tryComplete();
- }
-
- public void setJpegProduced() {
- if (DEBUG) {
- Log.d(TAG, "setJpegProduced - called for request " + mRequest.getRequestId());
- }
- if (!needsJpeg) {
- throw new IllegalStateException(
- "setJpegProduced called for capture with no jpeg targets.");
- }
- if (isCompleted()) {
- throw new IllegalStateException(
- "setJpegProduced called on already completed request.");
- }
-
- mReceivedFlags |= FLAG_RECEIVED_JPEG;
- tryComplete();
- }
-
- public void setJpegFailed() {
- if (DEBUG) {
- Log.d(TAG, "setJpegFailed - called for request " + mRequest.getRequestId());
- }
- if (!needsJpeg || isJpegCompleted()) {
- return;
- }
- mFailedJpeg = true;
-
- mReceivedFlags |= FLAG_RECEIVED_JPEG;
- mReceivedFlags |= FLAG_RECEIVED_JPEG_TS;
- tryComplete();
- }
-
- public void setPreviewTimestamp(long timestamp) {
- if (DEBUG) {
- Log.d(TAG, "setPreviewTimestamp - called for request " + mRequest.getRequestId());
- }
- if (!needsPreview) {
- throw new IllegalStateException(
- "setPreviewTimestamp called for capture with no preview targets.");
- }
- if (isCompleted()) {
- throw new IllegalStateException(
- "setPreviewTimestamp called on already completed request.");
- }
-
- mReceivedFlags |= FLAG_RECEIVED_PREVIEW_TS;
-
- if (mTimestamp == 0) {
- mTimestamp = timestamp;
- }
-
- if (!needsJpeg) {
- if (!mHasStarted) {
- mHasStarted = true;
- CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp,
- CameraDeviceState.NO_CAPTURE_ERROR);
- }
- }
-
- tryComplete();
- }
-
- public void setPreviewProduced() {
- if (DEBUG) {
- Log.d(TAG, "setPreviewProduced - called for request " + mRequest.getRequestId());
- }
- if (!needsPreview) {
- throw new IllegalStateException(
- "setPreviewProduced called for capture with no preview targets.");
- }
- if (isCompleted()) {
- throw new IllegalStateException(
- "setPreviewProduced called on already completed request.");
- }
-
- mReceivedFlags |= FLAG_RECEIVED_PREVIEW;
- tryComplete();
- }
-
- public void setPreviewFailed() {
- if (DEBUG) {
- Log.d(TAG, "setPreviewFailed - called for request " + mRequest.getRequestId());
- }
- if (!needsPreview || isPreviewCompleted()) {
- return;
- }
- mFailedPreview = true;
-
- mReceivedFlags |= FLAG_RECEIVED_PREVIEW;
- mReceivedFlags |= FLAG_RECEIVED_PREVIEW_TS;
- tryComplete();
- }
-
- // Comparison and equals based on frame number.
- @Override
- public int compareTo(CaptureHolder captureHolder) {
- return (mRequest.getFrameNumber() > captureHolder.mRequest.getFrameNumber()) ? 1 :
- ((mRequest.getFrameNumber() == captureHolder.mRequest.getFrameNumber()) ? 0 :
- -1);
- }
-
- // Comparison and equals based on frame number.
- @Override
- public boolean equals(Object o) {
- return o instanceof CaptureHolder && compareTo((CaptureHolder) o) == 0;
- }
- }
-
- private final TreeSet<CaptureHolder> mActiveRequests;
- private final ArrayDeque<CaptureHolder> mJpegCaptureQueue;
- private final ArrayDeque<CaptureHolder> mJpegProduceQueue;
- private final ArrayDeque<CaptureHolder> mPreviewCaptureQueue;
- private final ArrayDeque<CaptureHolder> mPreviewProduceQueue;
- private final ArrayList<CaptureHolder> mCompletedRequests = new ArrayList<>();
-
- private final ReentrantLock mLock = new ReentrantLock();
- private final Condition mIsEmpty;
- private final Condition mPreviewsEmpty;
- private final Condition mNotFull;
- private final CameraDeviceState mDeviceState;
- private int mInFlight = 0;
- private int mInFlightPreviews = 0;
- private final int mMaxInFlight;
-
- /**
- * Create a new {@link CaptureCollector} that can modify the given {@link CameraDeviceState}.
- *
- * @param maxInFlight max allowed in-flight requests.
- * @param deviceState the {@link CameraDeviceState} to update as requests are processed.
- */
- public CaptureCollector(int maxInFlight, CameraDeviceState deviceState) {
- mMaxInFlight = maxInFlight;
- mJpegCaptureQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT);
- mJpegProduceQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT);
- mPreviewCaptureQueue = new ArrayDeque<>(mMaxInFlight);
- mPreviewProduceQueue = new ArrayDeque<>(mMaxInFlight);
- mActiveRequests = new TreeSet<>();
- mIsEmpty = mLock.newCondition();
- mNotFull = mLock.newCondition();
- mPreviewsEmpty = mLock.newCondition();
- mDeviceState = deviceState;
- }
-
- /**
- * Queue a new request.
- *
- * <p>
- * For requests that use the Camera1 API preview output stream, this will block if there are
- * already {@code maxInFlight} requests in progress (until at least one prior request has
- * completed). For requests that use the Camera1 API jpeg callbacks, this will block until
- * all prior requests have been completed to avoid stopping preview for
- * {@link android.hardware.Camera#takePicture} before prior preview requests have been
- * completed.
- * </p>
- * @param holder the {@link RequestHolder} for this request.
- * @param legacy the {@link LegacyRequest} for this request; this will not be mutated.
- * @param timeout a timeout to use for this call.
- * @param unit the units to use for the timeout.
- * @return {@code false} if this method timed out.
- * @throws InterruptedException if this thread is interrupted.
- */
- public boolean queueRequest(RequestHolder holder, LegacyRequest legacy, long timeout,
- TimeUnit unit)
- throws InterruptedException {
- CaptureHolder h = new CaptureHolder(holder, legacy);
- long nanos = unit.toNanos(timeout);
- final ReentrantLock lock = this.mLock;
- lock.lock();
- try {
- if (DEBUG) {
- Log.d(TAG, "queueRequest for request " + holder.getRequestId() +
- " - " + mInFlight + " requests remain in flight.");
- }
-
- if (!(h.needsJpeg || h.needsPreview)) {
- throw new IllegalStateException("Request must target at least one output surface!");
- }
-
- if (h.needsJpeg) {
- // Wait for all current requests to finish before queueing jpeg.
- while (mInFlight > 0) {
- if (nanos <= 0) {
- return false;
- }
- nanos = mIsEmpty.awaitNanos(nanos);
- }
- mJpegCaptureQueue.add(h);
- mJpegProduceQueue.add(h);
- }
- if (h.needsPreview) {
- while (mInFlight >= mMaxInFlight) {
- if (nanos <= 0) {
- return false;
- }
- nanos = mNotFull.awaitNanos(nanos);
- }
- mPreviewCaptureQueue.add(h);
- mPreviewProduceQueue.add(h);
- mInFlightPreviews++;
- }
- mActiveRequests.add(h);
-
- mInFlight++;
- return true;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Wait all queued requests to complete.
- *
- * @param timeout a timeout to use for this call.
- * @param unit the units to use for the timeout.
- * @return {@code false} if this method timed out.
- * @throws InterruptedException if this thread is interrupted.
- */
- public boolean waitForEmpty(long timeout, TimeUnit unit) throws InterruptedException {
- long nanos = unit.toNanos(timeout);
- final ReentrantLock lock = this.mLock;
- lock.lock();
- try {
- while (mInFlight > 0) {
- if (nanos <= 0) {
- return false;
- }
- nanos = mIsEmpty.awaitNanos(nanos);
- }
- return true;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Wait all queued requests that use the Camera1 API preview output to complete.
- *
- * @param timeout a timeout to use for this call.
- * @param unit the units to use for the timeout.
- * @return {@code false} if this method timed out.
- * @throws InterruptedException if this thread is interrupted.
- */
- public boolean waitForPreviewsEmpty(long timeout, TimeUnit unit) throws InterruptedException {
- long nanos = unit.toNanos(timeout);
- final ReentrantLock lock = this.mLock;
- lock.lock();
- try {
- while (mInFlightPreviews > 0) {
- if (nanos <= 0) {
- return false;
- }
- nanos = mPreviewsEmpty.awaitNanos(nanos);
- }
- return true;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Wait for the specified request to be completed (all buffers available).
- *
- * <p>May not wait for the same request more than once, since a successful wait
- * will erase the history of that request.</p>
- *
- * @param holder the {@link RequestHolder} for this request.
- * @param timeout a timeout to use for this call.
- * @param unit the units to use for the timeout.
- * @param timestamp the timestamp of the request will be written out to here, in ns
- *
- * @return {@code false} if this method timed out.
- *
- * @throws InterruptedException if this thread is interrupted.
- */
- public boolean waitForRequestCompleted(RequestHolder holder, long timeout, TimeUnit unit,
- MutableLong timestamp)
- throws InterruptedException {
- long nanos = unit.toNanos(timeout);
- final ReentrantLock lock = this.mLock;
- lock.lock();
- try {
- while (!removeRequestIfCompleted(holder, /*out*/timestamp)) {
- if (nanos <= 0) {
- return false;
- }
- nanos = mNotFull.awaitNanos(nanos);
- }
- return true;
- } finally {
- lock.unlock();
- }
- }
-
- private boolean removeRequestIfCompleted(RequestHolder holder, MutableLong timestamp) {
- int i = 0;
- for (CaptureHolder h : mCompletedRequests) {
- if (h.mRequest.equals(holder)) {
- timestamp.value = h.mTimestamp;
- mCompletedRequests.remove(i);
- return true;
- }
- i++;
- }
-
- return false;
- }
-
- /**
- * Called to alert the {@link CaptureCollector} that the jpeg capture has begun.
- *
- * @param timestamp the time of the jpeg capture.
- * @return the {@link RequestHolder} for the request associated with this capture.
- */
- public RequestHolder jpegCaptured(long timestamp) {
- final ReentrantLock lock = this.mLock;
- lock.lock();
- try {
- CaptureHolder h = mJpegCaptureQueue.poll();
- if (h == null) {
- Log.w(TAG, "jpegCaptured called with no jpeg request on queue!");
- return null;
- }
- h.setJpegTimestamp(timestamp);
- return h.mRequest;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Called to alert the {@link CaptureCollector} that the jpeg capture has completed.
- *
- * @return a pair containing the {@link RequestHolder} and the timestamp of the capture.
- */
- public Pair<RequestHolder, Long> jpegProduced() {
- final ReentrantLock lock = this.mLock;
- lock.lock();
- try {
- CaptureHolder h = mJpegProduceQueue.poll();
- if (h == null) {
- Log.w(TAG, "jpegProduced called with no jpeg request on queue!");
- return null;
- }
- h.setJpegProduced();
- return new Pair<>(h.mRequest, h.mTimestamp);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Check if there are any pending capture requests that use the Camera1 API preview output.
- *
- * @return {@code true} if there are pending preview requests.
- */
- public boolean hasPendingPreviewCaptures() {
- final ReentrantLock lock = this.mLock;
- lock.lock();
- try {
- return !mPreviewCaptureQueue.isEmpty();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Called to alert the {@link CaptureCollector} that the preview capture has begun.
- *
- * @param timestamp the time of the preview capture.
- * @return a pair containing the {@link RequestHolder} and the timestamp of the capture.
- */
- public Pair<RequestHolder, Long> previewCaptured(long timestamp) {
- final ReentrantLock lock = this.mLock;
- lock.lock();
- try {
- CaptureHolder h = mPreviewCaptureQueue.poll();
- if (h == null) {
- if (DEBUG) {
- Log.d(TAG, "previewCaptured called with no preview request on queue!");
- }
- return null;
- }
- h.setPreviewTimestamp(timestamp);
- return new Pair<>(h.mRequest, h.mTimestamp);
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Called to alert the {@link CaptureCollector} that the preview capture has completed.
- *
- * @return the {@link RequestHolder} for the request associated with this capture.
- */
- public RequestHolder previewProduced() {
- final ReentrantLock lock = this.mLock;
- lock.lock();
- try {
- CaptureHolder h = mPreviewProduceQueue.poll();
- if (h == null) {
- Log.w(TAG, "previewProduced called with no preview request on queue!");
- return null;
- }
- h.setPreviewProduced();
- return h.mRequest;
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Called to alert the {@link CaptureCollector} that the next pending preview capture has failed.
- */
- public void failNextPreview() {
- final ReentrantLock lock = this.mLock;
- lock.lock();
- try {
- CaptureHolder h1 = mPreviewCaptureQueue.peek();
- CaptureHolder h2 = mPreviewProduceQueue.peek();
-
- // Find the request with the lowest frame number.
- CaptureHolder h = (h1 == null) ? h2 :
- ((h2 == null) ? h1 :
- ((h1.compareTo(h2) <= 0) ? h1 :
- h2));
-
- if (h != null) {
- mPreviewCaptureQueue.remove(h);
- mPreviewProduceQueue.remove(h);
- mActiveRequests.remove(h);
- h.setPreviewFailed();
- }
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Called to alert the {@link CaptureCollector} that the next pending jpeg capture has failed.
- */
- public void failNextJpeg() {
- final ReentrantLock lock = this.mLock;
- lock.lock();
- try {
- CaptureHolder h1 = mJpegCaptureQueue.peek();
- CaptureHolder h2 = mJpegProduceQueue.peek();
-
- // Find the request with the lowest frame number.
- CaptureHolder h = (h1 == null) ? h2 :
- ((h2 == null) ? h1 :
- ((h1.compareTo(h2) <= 0) ? h1 :
- h2));
-
- if (h != null) {
- mJpegCaptureQueue.remove(h);
- mJpegProduceQueue.remove(h);
- mActiveRequests.remove(h);
- h.setJpegFailed();
- }
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * Called to alert the {@link CaptureCollector} all pending captures have failed.
- */
- public void failAll() {
- final ReentrantLock lock = this.mLock;
- lock.lock();
- try {
- CaptureHolder h;
- while ((h = mActiveRequests.pollFirst()) != null) {
- h.setPreviewFailed();
- h.setJpegFailed();
- }
- mPreviewCaptureQueue.clear();
- mPreviewProduceQueue.clear();
- mJpegCaptureQueue.clear();
- mJpegProduceQueue.clear();
- } finally {
- lock.unlock();
- }
- }
-
- private void onPreviewCompleted() {
- mInFlightPreviews--;
- if (mInFlightPreviews < 0) {
- throw new IllegalStateException(
- "More preview captures completed than requests queued.");
- }
- if (mInFlightPreviews == 0) {
- mPreviewsEmpty.signalAll();
- }
- }
-
- private void onRequestCompleted(CaptureHolder capture) {
- RequestHolder request = capture.mRequest;
-
- mInFlight--;
- if (DEBUG) {
- Log.d(TAG, "Completed request " + request.getRequestId() +
- ", " + mInFlight + " requests remain in flight.");
- }
- if (mInFlight < 0) {
- throw new IllegalStateException(
- "More captures completed than requests queued.");
- }
-
- mCompletedRequests.add(capture);
- mActiveRequests.remove(capture);
-
- mNotFull.signalAll();
- if (mInFlight == 0) {
- mIsEmpty.signalAll();
- }
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/GLThreadManager.java b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
deleted file mode 100644
index 152d82d..0000000
--- a/core/java/android/hardware/camera2/legacy/GLThreadManager.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.graphics.SurfaceTexture;
-import android.hardware.camera2.impl.CameraDeviceImpl;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Size;
-import android.view.Surface;
-
-import java.util.Collection;
-
-import static com.android.internal.util.Preconditions.*;
-
-/**
- * GLThreadManager handles the thread used for rendering into the configured output surfaces.
- */
-public class GLThreadManager {
- private final String TAG;
- private static final boolean DEBUG = false;
-
- private static final int MSG_NEW_CONFIGURATION = 1;
- private static final int MSG_NEW_FRAME = 2;
- private static final int MSG_CLEANUP = 3;
- private static final int MSG_DROP_FRAMES = 4;
- private static final int MSG_ALLOW_FRAMES = 5;
-
- private CaptureCollector mCaptureCollector;
-
- private final CameraDeviceState mDeviceState;
-
- private final SurfaceTextureRenderer mTextureRenderer;
-
- private final RequestHandlerThread mGLHandlerThread;
-
- private final RequestThreadManager.FpsCounter mPrevCounter =
- new RequestThreadManager.FpsCounter("GL Preview Producer");
-
- /**
- * Container object for Configure messages.
- */
- private static class ConfigureHolder {
- public final ConditionVariable condition;
- public final Collection<Pair<Surface, Size>> surfaces;
- public final CaptureCollector collector;
-
- public ConfigureHolder(ConditionVariable condition, Collection<Pair<Surface,
- Size>> surfaces, CaptureCollector collector) {
- this.condition = condition;
- this.surfaces = surfaces;
- this.collector = collector;
- }
- }
-
- private final Handler.Callback mGLHandlerCb = new Handler.Callback() {
- private boolean mCleanup = false;
- private boolean mConfigured = false;
- private boolean mDroppingFrames = false;
-
- @SuppressWarnings("unchecked")
- @Override
- public boolean handleMessage(Message msg) {
- if (mCleanup) {
- return true;
- }
- try {
- switch (msg.what) {
- case MSG_NEW_CONFIGURATION:
- ConfigureHolder configure = (ConfigureHolder) msg.obj;
- mTextureRenderer.cleanupEGLContext();
- mTextureRenderer.configureSurfaces(configure.surfaces);
- mCaptureCollector = checkNotNull(configure.collector);
- configure.condition.open();
- mConfigured = true;
- break;
- case MSG_NEW_FRAME:
- if (mDroppingFrames) {
- Log.w(TAG, "Ignoring frame.");
- break;
- }
- if (DEBUG) {
- mPrevCounter.countAndLog();
- }
- if (!mConfigured) {
- Log.e(TAG, "Dropping frame, EGL context not configured!");
- }
- mTextureRenderer.drawIntoSurfaces(mCaptureCollector);
- break;
- case MSG_CLEANUP:
- mTextureRenderer.cleanupEGLContext();
- mCleanup = true;
- mConfigured = false;
- break;
- case MSG_DROP_FRAMES:
- mDroppingFrames = true;
- break;
- case MSG_ALLOW_FRAMES:
- mDroppingFrames = false;
- break;
- case RequestHandlerThread.MSG_POKE_IDLE_HANDLER:
- // OK: Ignore message.
- break;
- default:
- Log.e(TAG, "Unhandled message " + msg.what + " on GLThread.");
- break;
- }
- } catch (Exception e) {
- Log.e(TAG, "Received exception on GL render thread: ", e);
- mDeviceState.setError(CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
- }
- return true;
- }
- };
-
- /**
- * Create a new GL thread and renderer.
- *
- * @param cameraId the camera id for this thread.
- * @param facing direction the camera is facing.
- * @param state {@link CameraDeviceState} to use for error handling.
- */
- public GLThreadManager(int cameraId, int facing, CameraDeviceState state) {
- mTextureRenderer = new SurfaceTextureRenderer(facing);
- TAG = String.format("CameraDeviceGLThread-%d", cameraId);
- mGLHandlerThread = new RequestHandlerThread(TAG, mGLHandlerCb);
- mDeviceState = state;
- }
-
- /**
- * Start the thread.
- *
- * <p>
- * This must be called before queueing new frames.
- * </p>
- */
- public void start() {
- mGLHandlerThread.start();
- }
-
- /**
- * Wait until the thread has started.
- */
- public void waitUntilStarted() {
- mGLHandlerThread.waitUntilStarted();
- }
-
- /**
- * Quit the thread.
- *
- * <p>
- * No further methods can be called after this.
- * </p>
- */
- public void quit() {
- Handler handler = mGLHandlerThread.getHandler();
- handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
- mGLHandlerThread.quitSafely();
- try {
- mGLHandlerThread.join();
- } catch (InterruptedException e) {
- Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
- mGLHandlerThread.getName(), mGLHandlerThread.getId()));
- }
- }
-
- /**
- * Queue a new call to draw into the surfaces specified in the next available preview
- * request from the {@link CaptureCollector} passed to
- * {@link #setConfigurationAndWait(java.util.Collection, CaptureCollector)};
- */
- public void queueNewFrame() {
- Handler handler = mGLHandlerThread.getHandler();
-
- /**
- * Avoid queuing more than one new frame. If we are not consuming faster than frames
- * are produced, drop frames rather than allowing the queue to back up.
- */
- if (!handler.hasMessages(MSG_NEW_FRAME)) {
- handler.sendMessage(handler.obtainMessage(MSG_NEW_FRAME));
- } else {
- Log.e(TAG, "GLThread dropping frame. Not consuming frames quickly enough!");
- }
- }
-
- /**
- * Configure the GL renderer for the given set of output surfaces, and block until
- * this configuration has been applied.
- *
- * @param surfaces a collection of pairs of {@link android.view.Surface}s and their
- * corresponding sizes to configure.
- * @param collector a {@link CaptureCollector} to retrieve requests from.
- */
- public void setConfigurationAndWait(Collection<Pair<Surface, Size>> surfaces,
- CaptureCollector collector) {
- checkNotNull(collector, "collector must not be null");
- Handler handler = mGLHandlerThread.getHandler();
-
- final ConditionVariable condition = new ConditionVariable(/*closed*/false);
- ConfigureHolder configure = new ConfigureHolder(condition, surfaces, collector);
-
- Message m = handler.obtainMessage(MSG_NEW_CONFIGURATION, /*arg1*/0, /*arg2*/0, configure);
- handler.sendMessage(m);
-
- // Block until configuration applied.
- condition.block();
- }
-
- /**
- * Get the underlying surface to produce frames from.
- *
- * <p>
- * This returns the surface that is drawn into the set of surfaces passed in for each frame.
- * This method should only be called after a call to
- * {@link #setConfigurationAndWait(java.util.Collection)}. Calling this before the first call
- * to {@link #setConfigurationAndWait(java.util.Collection)}, after {@link #quit()}, or
- * concurrently to one of these calls may result in an invalid
- * {@link android.graphics.SurfaceTexture} being returned.
- * </p>
- *
- * @return an {@link android.graphics.SurfaceTexture} to draw to.
- */
- public SurfaceTexture getCurrentSurfaceTexture() {
- return mTextureRenderer.getSurfaceTexture();
- }
-
- /**
- * Ignore any subsequent calls to {@link #queueNewFrame(java.util.Collection)}.
- */
- public void ignoreNewFrames() {
- mGLHandlerThread.getHandler().sendEmptyMessage(MSG_DROP_FRAMES);
- }
-
- /**
- * Wait until no messages are queued.
- */
- public void waitUntilIdle() {
- mGLHandlerThread.waitUntilIdle();
- }
-
- /**
- * Re-enable drawing new frames after a call to {@link #ignoreNewFrames()}.
- */
- public void allowNewFrames() {
- mGLHandlerThread.getHandler().sendEmptyMessage(MSG_ALLOW_FRAMES);
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
deleted file mode 100644
index fdd578c..0000000
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ /dev/null
@@ -1,886 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.graphics.ImageFormat;
-import android.graphics.SurfaceTexture;
-import android.hardware.Camera;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.impl.CameraDeviceImpl;
-import android.hardware.camera2.impl.CaptureResultExtras;
-import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
-import android.hardware.camera2.ICameraDeviceCallbacks;
-import android.hardware.camera2.params.StreamConfigurationMap;
-import android.hardware.camera2.utils.ArrayUtils;
-import android.hardware.camera2.utils.SubmitInfo;
-import android.hardware.camera2.impl.CameraMetadataNative;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Size;
-import android.util.SparseArray;
-import android.view.Surface;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-import static android.hardware.camera2.legacy.LegacyExceptionUtils.*;
-import static com.android.internal.util.Preconditions.*;
-
-/**
- * This class emulates the functionality of a Camera2 device using a the old Camera class.
- *
- * <p>
- * There are two main components that are used to implement this:
- * - A state machine containing valid Camera2 device states ({@link CameraDeviceState}).
- * - A message-queue based pipeline that manages an old Camera class, and executes capture and
- * configuration requests.
- * </p>
- */
-public class LegacyCameraDevice implements AutoCloseable {
- private final String TAG;
-
- private static final boolean DEBUG = false;
- private final int mCameraId;
- private final CameraCharacteristics mStaticCharacteristics;
- private final ICameraDeviceCallbacks mDeviceCallbacks;
- private final CameraDeviceState mDeviceState = new CameraDeviceState();
- private SparseArray<Surface> mConfiguredSurfaces;
- private boolean mClosed = false;
-
- private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
-
- private final HandlerThread mResultThread = new HandlerThread("ResultThread");
- private final HandlerThread mCallbackHandlerThread = new HandlerThread("CallbackThread");
- private final Handler mCallbackHandler;
- private final Handler mResultHandler;
- private static final int ILLEGAL_VALUE = -1;
-
- // Keep up to date with values in hardware/libhardware/include/hardware/gralloc.h
- private static final int GRALLOC_USAGE_RENDERSCRIPT = 0x00100000;
- private static final int GRALLOC_USAGE_SW_READ_OFTEN = 0x00000003;
- private static final int GRALLOC_USAGE_HW_TEXTURE = 0x00000100;
- private static final int GRALLOC_USAGE_HW_COMPOSER = 0x00000800;
- private static final int GRALLOC_USAGE_HW_RENDER = 0x00000200;
- private static final int GRALLOC_USAGE_HW_VIDEO_ENCODER = 0x00010000;
-
- public static final int MAX_DIMEN_FOR_ROUNDING = 1920; // maximum allowed width for rounding
-
- // Keep up to date with values in system/core/include/system/window.h
- public static final int NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW = 1;
-
- private CaptureResultExtras getExtrasFromRequest(RequestHolder holder) {
- return getExtrasFromRequest(holder,
- /*errorCode*/CameraDeviceState.NO_CAPTURE_ERROR, /*errorArg*/null);
- }
-
- private CaptureResultExtras getExtrasFromRequest(RequestHolder holder,
- int errorCode, Object errorArg) {
- int errorStreamId = -1;
- if (errorCode == CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_BUFFER) {
- Surface errorTarget = (Surface) errorArg;
- int indexOfTarget = mConfiguredSurfaces.indexOfValue(errorTarget);
- if (indexOfTarget < 0) {
- Log.e(TAG, "Buffer drop error reported for unknown Surface");
- } else {
- errorStreamId = mConfiguredSurfaces.keyAt(indexOfTarget);
- }
- }
- if (holder == null) {
- return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE,
- ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, null,
- ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE);
- }
- return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(),
- /*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber(),
- /*partialResultCount*/1, errorStreamId, null, holder.getFrameNumber(), -1, -1);
- }
-
- /**
- * Listener for the camera device state machine. Calls the appropriate
- * {@link ICameraDeviceCallbacks} for each state transition.
- */
- private final CameraDeviceState.CameraDeviceStateListener mStateListener =
- new CameraDeviceState.CameraDeviceStateListener() {
- @Override
- public void onError(final int errorCode, final Object errorArg, final RequestHolder holder) {
- if (DEBUG) {
- Log.d(TAG, "onError called, errorCode = " + errorCode + ", errorArg = " + errorArg);
- }
- switch (errorCode) {
- /*
- * Only be considered idle if we hit a fatal error
- * and no further requests can be processed.
- */
- case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DISCONNECTED:
- case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_SERVICE:
- case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE: {
- mIdle.open();
-
- if (DEBUG) {
- Log.d(TAG, "onError - opening idle");
- }
- }
- }
-
- final CaptureResultExtras extras = getExtrasFromRequest(holder, errorCode, errorArg);
- mResultHandler.post(new Runnable() {
- @Override
- public void run() {
- if (DEBUG) {
- Log.d(TAG, "doing onError callback for request " + holder.getRequestId() +
- ", with error code " + errorCode);
- }
- try {
- mDeviceCallbacks.onDeviceError(errorCode, extras);
- } catch (RemoteException e) {
- throw new IllegalStateException(
- "Received remote exception during onCameraError callback: ", e);
- }
- }
- });
- }
-
- @Override
- public void onConfiguring() {
- // Do nothing
- if (DEBUG) {
- Log.d(TAG, "doing onConfiguring callback.");
- }
- }
-
- @Override
- public void onIdle() {
- if (DEBUG) {
- Log.d(TAG, "onIdle called");
- }
-
- mIdle.open();
-
- mResultHandler.post(new Runnable() {
- @Override
- public void run() {
- if (DEBUG) {
- Log.d(TAG, "doing onIdle callback.");
- }
- try {
- mDeviceCallbacks.onDeviceIdle();
- } catch (RemoteException e) {
- throw new IllegalStateException(
- "Received remote exception during onCameraIdle callback: ", e);
- }
- }
- });
- }
-
- @Override
- public void onBusy() {
- mIdle.close();
-
- if (DEBUG) {
- Log.d(TAG, "onBusy called");
- }
- }
-
- @Override
- public void onCaptureStarted(final RequestHolder holder, final long timestamp) {
- final CaptureResultExtras extras = getExtrasFromRequest(holder);
-
- mResultHandler.post(new Runnable() {
- @Override
- public void run() {
- if (DEBUG) {
- Log.d(TAG, "doing onCaptureStarted callback for request " +
- holder.getRequestId());
- }
- try {
- mDeviceCallbacks.onCaptureStarted(extras, timestamp);
- } catch (RemoteException e) {
- throw new IllegalStateException(
- "Received remote exception during onCameraError callback: ", e);
- }
- }
- });
- }
-
- @Override
- public void onRequestQueueEmpty() {
- mResultHandler.post(new Runnable() {
- @Override
- public void run() {
- if (DEBUG) {
- Log.d(TAG, "doing onRequestQueueEmpty callback");
- }
- try {
- mDeviceCallbacks.onRequestQueueEmpty();
- } catch (RemoteException e) {
- throw new IllegalStateException(
- "Received remote exception during onRequestQueueEmpty callback: ",
- e);
- }
- }
- });
- }
-
- @Override
- public void onCaptureResult(final CameraMetadataNative result, final RequestHolder holder) {
- final CaptureResultExtras extras = getExtrasFromRequest(holder);
-
- mResultHandler.post(new Runnable() {
- @Override
- public void run() {
- if (DEBUG) {
- Log.d(TAG, "doing onCaptureResult callback for request " +
- holder.getRequestId());
- }
- try {
- mDeviceCallbacks.onResultReceived(result, extras,
- new PhysicalCaptureResultInfo[0]);
- } catch (RemoteException e) {
- throw new IllegalStateException(
- "Received remote exception during onCameraError callback: ", e);
- }
- }
- });
- }
-
- @Override
- public void onRepeatingRequestError(final long lastFrameNumber,
- final int repeatingRequestId) {
- mResultHandler.post(new Runnable() {
- @Override
- public void run() {
- if (DEBUG) {
- Log.d(TAG, "doing onRepeatingRequestError callback.");
- }
- try {
- mDeviceCallbacks.onRepeatingRequestError(lastFrameNumber,
- repeatingRequestId);
- } catch (RemoteException e) {
- throw new IllegalStateException(
- "Received remote exception during onRepeatingRequestError " +
- "callback: ", e);
- }
- }
- });
- }
- };
-
- private final RequestThreadManager mRequestThreadManager;
-
- /**
- * Check if a given surface uses {@link ImageFormat#YUV_420_888} or format that can be readily
- * converted to this; YV12 and NV21 are the two currently supported formats.
- *
- * @param s the surface to check.
- * @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888} or a compatible
- * format.
- */
- static boolean needsConversion(Surface s) throws BufferQueueAbandonedException {
- int nativeType = detectSurfaceType(s);
- return nativeType == ImageFormat.YUV_420_888 || nativeType == ImageFormat.YV12 ||
- nativeType == ImageFormat.NV21;
- }
-
- /**
- * Create a new emulated camera device from a given Camera 1 API camera.
- *
- * <p>
- * The {@link Camera} provided to this constructor must already have been successfully opened,
- * and ownership of the provided camera is passed to this object. No further calls to the
- * camera methods should be made following this constructor.
- * </p>
- *
- * @param cameraId the id of the camera.
- * @param camera an open {@link Camera} device.
- * @param characteristics the static camera characteristics for this camera device
- * @param callbacks {@link ICameraDeviceCallbacks} callbacks to call for Camera2 API operations.
- */
- public LegacyCameraDevice(int cameraId, Camera camera, CameraCharacteristics characteristics,
- ICameraDeviceCallbacks callbacks) {
- mCameraId = cameraId;
- mDeviceCallbacks = callbacks;
- TAG = String.format("CameraDevice-%d-LE", mCameraId);
-
- mResultThread.start();
- mResultHandler = new Handler(mResultThread.getLooper());
- mCallbackHandlerThread.start();
- mCallbackHandler = new Handler(mCallbackHandlerThread.getLooper());
- mDeviceState.setCameraDeviceCallbacks(mCallbackHandler, mStateListener);
- mStaticCharacteristics = characteristics;
- mRequestThreadManager =
- new RequestThreadManager(cameraId, camera, characteristics, mDeviceState);
- mRequestThreadManager.start();
- }
-
- /**
- * Configure the device with a set of output surfaces.
- *
- * <p>Using empty or {@code null} {@code outputs} is the same as unconfiguring.</p>
- *
- * <p>Every surface in {@code outputs} must be non-{@code null}.</p>
- *
- * @param outputs a list of surfaces to set. LegacyCameraDevice will take ownership of this
- * list; it must not be modified by the caller once it's passed in.
- * @return an error code for this binder operation, or {@link NO_ERROR}
- * on success.
- */
- public int configureOutputs(SparseArray<Surface> outputs) {
- return configureOutputs(outputs, /*validateSurfacesOnly*/false);
- }
-
- /**
- * Configure the device with a set of output surfaces.
- *
- * <p>Using empty or {@code null} {@code outputs} is the same as unconfiguring.</p>
- *
- * <p>Every surface in {@code outputs} must be non-{@code null}.</p>
- *
- * @param outputs a list of surfaces to set. LegacyCameraDevice will take ownership of this
- * list; it must not be modified by the caller once it's passed in.
- * @param validateSurfacesOnly If set it will only check whether the outputs are supported
- * and avoid any device configuration.
- * @return an error code for this binder operation, or {@link NO_ERROR}
- * on success.
- * @hide
- */
- public int configureOutputs(SparseArray<Surface> outputs, boolean validateSurfacesOnly) {
- List<Pair<Surface, Size>> sizedSurfaces = new ArrayList<>();
- if (outputs != null) {
- int count = outputs.size();
- for (int i = 0; i < count; i++) {
- Surface output = outputs.valueAt(i);
- if (output == null) {
- Log.e(TAG, "configureOutputs - null outputs are not allowed");
- return BAD_VALUE;
- }
- if (!output.isValid()) {
- Log.e(TAG, "configureOutputs - invalid output surfaces are not allowed");
- return BAD_VALUE;
- }
- StreamConfigurationMap streamConfigurations = mStaticCharacteristics.
- get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-
- // Validate surface size and format.
- try {
- Size s = getSurfaceSize(output);
- int surfaceType = detectSurfaceType(output);
-
- boolean flexibleConsumer = isFlexibleConsumer(output);
-
- Size[] sizes = streamConfigurations.getOutputSizes(surfaceType);
- if (sizes == null) {
- if (surfaceType == ImageFormat.PRIVATE) {
-
- // YUV_420_888 is always present in LEGACY for all
- // IMPLEMENTATION_DEFINED output sizes, and is publicly visible in the
- // API (i.e. {@code #getOutputSizes} works here).
- sizes = streamConfigurations.getOutputSizes(ImageFormat.YUV_420_888);
- } else if (surfaceType == LegacyMetadataMapper.HAL_PIXEL_FORMAT_BLOB) {
- sizes = streamConfigurations.getOutputSizes(ImageFormat.JPEG);
- }
- }
-
- if (!ArrayUtils.contains(sizes, s)) {
- if (flexibleConsumer && (s = findClosestSize(s, sizes)) != null) {
- sizedSurfaces.add(new Pair<>(output, s));
- } else {
- String reason = (sizes == null) ? "format is invalid." :
- ("size not in valid set: " + Arrays.toString(sizes));
- Log.e(TAG, String.format("Surface with size (w=%d, h=%d) and format " +
- "0x%x is not valid, %s", s.getWidth(), s.getHeight(),
- surfaceType, reason));
- return BAD_VALUE;
- }
- } else {
- sizedSurfaces.add(new Pair<>(output, s));
- }
- // Lock down the size before configuration
- if (!validateSurfacesOnly) {
- setSurfaceDimens(output, s.getWidth(), s.getHeight());
- }
- } catch (BufferQueueAbandonedException e) {
- Log.e(TAG, "Surface bufferqueue is abandoned, cannot configure as output: ", e);
- return BAD_VALUE;
- }
-
- }
- }
-
- if (validateSurfacesOnly) {
- return LegacyExceptionUtils.NO_ERROR;
- }
-
- boolean success = false;
- if (mDeviceState.setConfiguring()) {
- mRequestThreadManager.configure(sizedSurfaces);
- success = mDeviceState.setIdle();
- }
-
- if (success) {
- mConfiguredSurfaces = outputs;
- } else {
- return LegacyExceptionUtils.INVALID_OPERATION;
- }
- return LegacyExceptionUtils.NO_ERROR;
- }
-
- /**
- * Submit a burst of capture requests.
- *
- * @param requestList a list of capture requests to execute.
- * @param repeating {@code true} if this burst is repeating.
- * @return the submission info, including the new request id, and the last frame number, which
- * contains either the frame number of the last frame that will be returned for this request,
- * or the frame number of the last frame that will be returned for the current repeating
- * request if this burst is set to be repeating.
- */
- public SubmitInfo submitRequestList(CaptureRequest[] requestList, boolean repeating) {
- if (requestList == null || requestList.length == 0) {
- Log.e(TAG, "submitRequestList - Empty/null requests are not allowed");
- throw new ServiceSpecificException(BAD_VALUE,
- "submitRequestList - Empty/null requests are not allowed");
- }
-
- List<Long> surfaceIds;
-
- try {
- surfaceIds = (mConfiguredSurfaces == null) ? new ArrayList<Long>() :
- getSurfaceIds(mConfiguredSurfaces);
- } catch (BufferQueueAbandonedException e) {
- throw new ServiceSpecificException(BAD_VALUE,
- "submitRequestList - configured surface is abandoned.");
- }
-
- // Make sure that there all requests have at least 1 surface; all surfaces are non-null
- for (CaptureRequest request : requestList) {
- if (request.getTargets().isEmpty()) {
- Log.e(TAG, "submitRequestList - "
- + "Each request must have at least one Surface target");
- throw new ServiceSpecificException(BAD_VALUE,
- "submitRequestList - "
- + "Each request must have at least one Surface target");
- }
-
- for (Surface surface : request.getTargets()) {
- if (surface == null) {
- Log.e(TAG, "submitRequestList - Null Surface targets are not allowed");
- throw new ServiceSpecificException(BAD_VALUE,
- "submitRequestList - Null Surface targets are not allowed");
- } else if (mConfiguredSurfaces == null) {
- Log.e(TAG, "submitRequestList - must configure " +
- " device with valid surfaces before submitting requests");
- throw new ServiceSpecificException(INVALID_OPERATION,
- "submitRequestList - must configure " +
- " device with valid surfaces before submitting requests");
- } else if (!containsSurfaceId(surface, surfaceIds)) {
- Log.e(TAG, "submitRequestList - cannot use a surface that wasn't configured");
- throw new ServiceSpecificException(BAD_VALUE,
- "submitRequestList - cannot use a surface that wasn't configured");
- }
- }
- }
-
- // TODO: further validation of request here
- mIdle.close();
- return mRequestThreadManager.submitCaptureRequests(requestList, repeating);
- }
-
- /**
- * Submit a single capture request.
- *
- * @param request the capture request to execute.
- * @param repeating {@code true} if this request is repeating.
- * @return the submission info, including the new request id, and the last frame number, which
- * contains either the frame number of the last frame that will be returned for this request,
- * or the frame number of the last frame that will be returned for the current repeating
- * request if this burst is set to be repeating.
- */
- public SubmitInfo submitRequest(CaptureRequest request, boolean repeating) {
- CaptureRequest[] requestList = { request };
- return submitRequestList(requestList, repeating);
- }
-
- /**
- * Cancel the repeating request with the given request id.
- *
- * @param requestId the request id of the request to cancel.
- * @return the last frame number to be returned from the HAL for the given repeating request, or
- * {@code INVALID_FRAME} if none exists.
- */
- public long cancelRequest(int requestId) {
- return mRequestThreadManager.cancelRepeating(requestId);
- }
-
- /**
- * Block until the {@link ICameraDeviceCallbacks#onCameraIdle()} callback is received.
- */
- public void waitUntilIdle() {
- mIdle.block();
- }
-
- /**
- * Flush any pending requests.
- *
- * @return the last frame number.
- */
- public long flush() {
- long lastFrame = mRequestThreadManager.flush();
- waitUntilIdle();
- return lastFrame;
- }
-
- public void setAudioRestriction(int mode) {
- mRequestThreadManager.setAudioRestriction(mode);
- }
-
- public int getAudioRestriction() {
- return mRequestThreadManager.getAudioRestriction();
- }
-
- /**
- * Return {@code true} if the device has been closed.
- */
- public boolean isClosed() {
- return mClosed;
- }
-
- @Override
- public void close() {
- mRequestThreadManager.quit();
- mCallbackHandlerThread.quitSafely();
- mResultThread.quitSafely();
-
- try {
- mCallbackHandlerThread.join();
- } catch (InterruptedException e) {
- Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
- mCallbackHandlerThread.getName(), mCallbackHandlerThread.getId()));
- }
-
- try {
- mResultThread.join();
- } catch (InterruptedException e) {
- Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
- mResultThread.getName(), mResultThread.getId()));
- }
-
- mClosed = true;
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- close();
- } catch (ServiceSpecificException e) {
- Log.e(TAG, "Got error while trying to finalize, ignoring: " + e.getMessage());
- } finally {
- super.finalize();
- }
- }
-
- static long findEuclidDistSquare(Size a, Size b) {
- long d0 = a.getWidth() - b.getWidth();
- long d1 = a.getHeight() - b.getHeight();
- return d0 * d0 + d1 * d1;
- }
-
- // Keep up to date with rounding behavior in
- // frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
- static Size findClosestSize(Size size, Size[] supportedSizes) {
- if (size == null || supportedSizes == null) {
- return null;
- }
- Size bestSize = null;
- for (Size s : supportedSizes) {
- if (s.equals(size)) {
- return size;
- } else if (s.getWidth() <= MAX_DIMEN_FOR_ROUNDING && (bestSize == null ||
- LegacyCameraDevice.findEuclidDistSquare(size, s) <
- LegacyCameraDevice.findEuclidDistSquare(bestSize, s))) {
- bestSize = s;
- }
- }
- return bestSize;
- }
-
- /**
- * Query the surface for its currently configured default buffer size.
- * @param surface a non-{@code null} {@code Surface}
- * @return the width and height of the surface
- *
- * @throws NullPointerException if the {@code surface} was {@code null}
- * @throws BufferQueueAbandonedException if the {@code surface} was invalid
- */
- public static Size getSurfaceSize(Surface surface) throws BufferQueueAbandonedException {
- checkNotNull(surface);
-
- int[] dimens = new int[2];
- LegacyExceptionUtils.throwOnError(nativeDetectSurfaceDimens(surface, /*out*/dimens));
-
- return new Size(dimens[0], dimens[1]);
- }
-
- public static boolean isFlexibleConsumer(Surface output) {
- int usageFlags = detectSurfaceUsageFlags(output);
-
- // Keep up to date with allowed consumer types in
- // frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
- int disallowedFlags = GRALLOC_USAGE_HW_VIDEO_ENCODER | GRALLOC_USAGE_RENDERSCRIPT;
- int allowedFlags = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_SW_READ_OFTEN |
- GRALLOC_USAGE_HW_COMPOSER;
- boolean flexibleConsumer = ((usageFlags & disallowedFlags) == 0 &&
- (usageFlags & allowedFlags) != 0);
- return flexibleConsumer;
- }
-
- public static boolean isPreviewConsumer(Surface output) {
- int usageFlags = detectSurfaceUsageFlags(output);
- int disallowedFlags = GRALLOC_USAGE_HW_VIDEO_ENCODER | GRALLOC_USAGE_RENDERSCRIPT |
- GRALLOC_USAGE_SW_READ_OFTEN;
- int allowedFlags = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_COMPOSER |
- GRALLOC_USAGE_HW_RENDER;
- boolean previewConsumer = ((usageFlags & disallowedFlags) == 0 &&
- (usageFlags & allowedFlags) != 0);
- int surfaceFormat = ImageFormat.UNKNOWN;
- try {
- surfaceFormat = detectSurfaceType(output);
- } catch(BufferQueueAbandonedException e) {
- throw new IllegalArgumentException("Surface was abandoned", e);
- }
-
- return previewConsumer;
- }
-
- public static boolean isVideoEncoderConsumer(Surface output) {
- int usageFlags = detectSurfaceUsageFlags(output);
- int disallowedFlags = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_COMPOSER |
- GRALLOC_USAGE_RENDERSCRIPT | GRALLOC_USAGE_SW_READ_OFTEN;
- int allowedFlags = GRALLOC_USAGE_HW_VIDEO_ENCODER;
- boolean videoEncoderConsumer = ((usageFlags & disallowedFlags) == 0 &&
- (usageFlags & allowedFlags) != 0);
-
- int surfaceFormat = ImageFormat.UNKNOWN;
- try {
- surfaceFormat = detectSurfaceType(output);
- } catch(BufferQueueAbandonedException e) {
- throw new IllegalArgumentException("Surface was abandoned", e);
- }
-
- return videoEncoderConsumer;
- }
-
- /**
- * Query the surface for its currently configured usage flags
- */
- static int detectSurfaceUsageFlags(Surface surface) {
- checkNotNull(surface);
- return nativeDetectSurfaceUsageFlags(surface);
- }
-
- /**
- * Query the surface for its currently configured format
- */
- public static int detectSurfaceType(Surface surface) throws BufferQueueAbandonedException {
- checkNotNull(surface);
- int surfaceType = nativeDetectSurfaceType(surface);
-
- // TODO: remove this override since the default format should be
- // ImageFormat.PRIVATE. b/9487482
- if ((surfaceType >= LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888 &&
- surfaceType <= LegacyMetadataMapper.HAL_PIXEL_FORMAT_BGRA_8888)) {
- surfaceType = ImageFormat.PRIVATE;
- }
-
- return LegacyExceptionUtils.throwOnError(surfaceType);
- }
-
- /**
- * Query the surface for its currently configured dataspace
- */
- public static int detectSurfaceDataspace(Surface surface) throws BufferQueueAbandonedException {
- checkNotNull(surface);
- return LegacyExceptionUtils.throwOnError(nativeDetectSurfaceDataspace(surface));
- }
-
- static void connectSurface(Surface surface) throws BufferQueueAbandonedException {
- checkNotNull(surface);
-
- LegacyExceptionUtils.throwOnError(nativeConnectSurface(surface));
- }
-
- static void disconnectSurface(Surface surface) throws BufferQueueAbandonedException {
- if (surface == null) return;
-
- LegacyExceptionUtils.throwOnError(nativeDisconnectSurface(surface));
- }
-
- static void produceFrame(Surface surface, byte[] pixelBuffer, int width,
- int height, int pixelFormat)
- throws BufferQueueAbandonedException {
- checkNotNull(surface);
- checkNotNull(pixelBuffer);
- checkArgumentPositive(width, "width must be positive.");
- checkArgumentPositive(height, "height must be positive.");
-
- LegacyExceptionUtils.throwOnError(nativeProduceFrame(surface, pixelBuffer, width, height,
- pixelFormat));
- }
-
- static void setSurfaceFormat(Surface surface, int pixelFormat)
- throws BufferQueueAbandonedException {
- checkNotNull(surface);
-
- LegacyExceptionUtils.throwOnError(nativeSetSurfaceFormat(surface, pixelFormat));
- }
-
- static void setSurfaceDimens(Surface surface, int width, int height)
- throws BufferQueueAbandonedException {
- checkNotNull(surface);
- checkArgumentPositive(width, "width must be positive.");
- checkArgumentPositive(height, "height must be positive.");
-
- LegacyExceptionUtils.throwOnError(nativeSetSurfaceDimens(surface, width, height));
- }
-
- public static long getSurfaceId(Surface surface) throws BufferQueueAbandonedException {
- checkNotNull(surface);
- try {
- return nativeGetSurfaceId(surface);
- } catch (IllegalArgumentException e) {
- throw new BufferQueueAbandonedException();
- }
- }
-
- static List<Long> getSurfaceIds(SparseArray<Surface> surfaces)
- throws BufferQueueAbandonedException {
- if (surfaces == null) {
- throw new NullPointerException("Null argument surfaces");
- }
- List<Long> surfaceIds = new ArrayList<>();
- int count = surfaces.size();
- for (int i = 0; i < count; i++) {
- long id = getSurfaceId(surfaces.valueAt(i));
- if (id == 0) {
- throw new IllegalStateException(
- "Configured surface had null native GraphicBufferProducer pointer!");
- }
- surfaceIds.add(id);
- }
- return surfaceIds;
- }
-
- static List<Long> getSurfaceIds(Collection<Surface> surfaces)
- throws BufferQueueAbandonedException {
- if (surfaces == null) {
- throw new NullPointerException("Null argument surfaces");
- }
- List<Long> surfaceIds = new ArrayList<>();
- for (Surface s : surfaces) {
- long id = getSurfaceId(s);
- if (id == 0) {
- throw new IllegalStateException(
- "Configured surface had null native GraphicBufferProducer pointer!");
- }
- surfaceIds.add(id);
- }
- return surfaceIds;
- }
-
- static boolean containsSurfaceId(Surface s, Collection<Long> ids) {
- long id = 0;
- try {
- id = getSurfaceId(s);
- } catch (BufferQueueAbandonedException e) {
- // If surface is abandoned, return false.
- return false;
- }
- return ids.contains(id);
- }
-
- static void setSurfaceOrientation(Surface surface, int facing, int sensorOrientation)
- throws BufferQueueAbandonedException {
- checkNotNull(surface);
- LegacyExceptionUtils.throwOnError(nativeSetSurfaceOrientation(surface, facing,
- sensorOrientation));
- }
-
- static Size getTextureSize(SurfaceTexture surfaceTexture)
- throws BufferQueueAbandonedException {
- checkNotNull(surfaceTexture);
-
- int[] dimens = new int[2];
- LegacyExceptionUtils.throwOnError(nativeDetectTextureDimens(surfaceTexture,
- /*out*/dimens));
-
- return new Size(dimens[0], dimens[1]);
- }
-
- static void setNextTimestamp(Surface surface, long timestamp)
- throws BufferQueueAbandonedException {
- checkNotNull(surface);
- LegacyExceptionUtils.throwOnError(nativeSetNextTimestamp(surface, timestamp));
- }
-
- static void setScalingMode(Surface surface, int mode)
- throws BufferQueueAbandonedException {
- checkNotNull(surface);
- LegacyExceptionUtils.throwOnError(nativeSetScalingMode(surface, mode));
- }
-
-
- private static native int nativeDetectSurfaceType(Surface surface);
-
- private static native int nativeDetectSurfaceDataspace(Surface surface);
-
- private static native int nativeDetectSurfaceDimens(Surface surface,
- /*out*/int[/*2*/] dimens);
-
- private static native int nativeConnectSurface(Surface surface);
-
- private static native int nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
- int height, int pixelFormat);
-
- private static native int nativeSetSurfaceFormat(Surface surface, int pixelFormat);
-
- private static native int nativeSetSurfaceDimens(Surface surface, int width, int height);
-
- private static native long nativeGetSurfaceId(Surface surface);
-
- private static native int nativeSetSurfaceOrientation(Surface surface, int facing,
- int sensorOrientation);
-
- private static native int nativeDetectTextureDimens(SurfaceTexture surfaceTexture,
- /*out*/int[/*2*/] dimens);
-
- private static native int nativeSetNextTimestamp(Surface surface, long timestamp);
-
- private static native int nativeDetectSurfaceUsageFlags(Surface surface);
-
- private static native int nativeSetScalingMode(Surface surface, int scalingMode);
-
- private static native int nativeDisconnectSurface(Surface surface);
-
- static native int nativeGetJpegFooterSize();
-}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java b/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java
deleted file mode 100644
index 55130c8..0000000
--- a/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.hardware.ICameraService;
-import android.os.ServiceSpecificException;
-import android.util.AndroidException;
-
-import static android.system.OsConstants.*;
-
-/**
- * Utility class containing exception handling used solely by the compatibility mode shim.
- */
-public class LegacyExceptionUtils {
- private static final String TAG = "LegacyExceptionUtils";
-
- public static final int NO_ERROR = 0;
- public static final int PERMISSION_DENIED = -EPERM;
- public static final int ALREADY_EXISTS = -EEXIST;
- public static final int BAD_VALUE = -EINVAL;
- public static final int DEAD_OBJECT = -ENOSYS;
- public static final int INVALID_OPERATION = -EPIPE;
- public static final int TIMED_OUT = -ETIMEDOUT;
-
- /**
- * Checked exception thrown when a BufferQueue has been abandoned by its consumer.
- */
- public static class BufferQueueAbandonedException extends AndroidException {
- public BufferQueueAbandonedException () {}
-
- public BufferQueueAbandonedException(String name) {
- super(name);
- }
-
- public BufferQueueAbandonedException(String name, Throwable cause) {
- super(name, cause);
- }
-
- public BufferQueueAbandonedException(Exception cause) {
- super(cause);
- }
- }
-
- /**
- * Throw error codes used by legacy device methods as exceptions.
- *
- * <p>Non-negative return values are passed through, negative return values are thrown as
- * exceptions.</p>
- *
- * @param errorFlag error to throw as an exception.
- * @throws {@link BufferQueueAbandonedException} for BAD_VALUE.
- * @throws {@link UnsupportedOperationException} for an unknown negative error code.
- * @return {@code errorFlag} if the value was non-negative, throws otherwise.
- */
- public static int throwOnError(int errorFlag) throws BufferQueueAbandonedException {
- if (errorFlag == NO_ERROR) {
- return NO_ERROR;
- } else if (errorFlag == BAD_VALUE) {
- throw new BufferQueueAbandonedException();
- }
-
- if (errorFlag < 0) {
- throw new UnsupportedOperationException("Unknown error " + errorFlag);
- }
- return errorFlag;
- }
-
- /**
- * Throw error codes returned by the camera service as exceptions.
- *
- * @param errorFlag error to throw as an exception.
- */
- public static void throwOnServiceError(int errorFlag) {
- int errorCode = ICameraService.ERROR_INVALID_OPERATION;
- String errorMsg;
-
- if (errorFlag >= NO_ERROR) {
- return;
- } else if (errorFlag == PERMISSION_DENIED) {
- errorCode = ICameraService.ERROR_PERMISSION_DENIED;
- errorMsg = "Lacking privileges to access camera service";
- } else if (errorFlag == ALREADY_EXISTS) {
- // This should be handled at the call site. Typically this isn't bad,
- // just means we tried to do an operation that already completed.
- return;
- } else if (errorFlag == BAD_VALUE) {
- errorCode = ICameraService.ERROR_ILLEGAL_ARGUMENT;
- errorMsg = "Bad argument passed to camera service";
- } else if (errorFlag == DEAD_OBJECT) {
- errorCode = ICameraService.ERROR_DISCONNECTED;
- errorMsg = "Camera service not available";
- } else if (errorFlag == TIMED_OUT) {
- errorCode = ICameraService.ERROR_INVALID_OPERATION;
- errorMsg = "Operation timed out in camera service";
- } else if (errorFlag == -EACCES) {
- errorCode = ICameraService.ERROR_DISABLED;
- errorMsg = "Camera disabled by policy";
- } else if (errorFlag == -EBUSY) {
- errorCode = ICameraService.ERROR_CAMERA_IN_USE;
- errorMsg = "Camera already in use";
- } else if (errorFlag == -EUSERS) {
- errorCode = ICameraService.ERROR_MAX_CAMERAS_IN_USE;
- errorMsg = "Maximum number of cameras in use";
- } else if (errorFlag == -ENODEV) {
- errorCode = ICameraService.ERROR_DISCONNECTED;
- errorMsg = "Camera device not available";
- } else if (errorFlag == -EOPNOTSUPP) {
- errorCode = ICameraService.ERROR_DEPRECATED_HAL;
- errorMsg = "Deprecated camera HAL does not support this";
- } else if (errorFlag == INVALID_OPERATION) {
- errorCode = ICameraService.ERROR_INVALID_OPERATION;
- errorMsg = "Illegal state encountered in camera service.";
- } else {
- errorCode = ICameraService.ERROR_INVALID_OPERATION;
- errorMsg = "Unknown camera device error " + errorFlag;
- }
-
- throw new ServiceSpecificException(errorCode, errorMsg);
- }
-
- private LegacyExceptionUtils() {
- throw new AssertionError();
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java b/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java
deleted file mode 100644
index b3b4549..0000000
--- a/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.graphics.Rect;
-import android.hardware.Camera;
-import android.hardware.Camera.FaceDetectionListener;
-import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.params.Face;
-import android.hardware.camera2.utils.ListUtils;
-import android.hardware.camera2.utils.ParamsUtils;
-import android.util.Log;
-import android.util.Size;
-
-import com.android.internal.util.ArrayUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static android.hardware.camera2.CaptureRequest.*;
-import static com.android.internal.util.Preconditions.*;
-
-/**
- * Map legacy face detect callbacks into face detection results.
- */
-@SuppressWarnings("deprecation")
-public class LegacyFaceDetectMapper {
- private static String TAG = "LegacyFaceDetectMapper";
- private static final boolean DEBUG = false;
-
- private final Camera mCamera;
- /** Is the camera capable of face detection? */
- private final boolean mFaceDetectSupported;
- /** Is the camera is running face detection? */
- private boolean mFaceDetectEnabled = false;
- /** Did the last request say to use SCENE_MODE = FACE_PRIORITY? */
- private boolean mFaceDetectScenePriority = false;
- /** Did the last request enable the face detect mode to ON? */
- private boolean mFaceDetectReporting = false;
-
- /** Synchronize access to all fields */
- private final Object mLock = new Object();
- private Camera.Face[] mFaces;
- private Camera.Face[] mFacesPrev;
- /**
- * Instantiate a new face detect mapper.
- *
- * @param camera a non-{@code null} camera1 device
- * @param characteristics a non-{@code null} camera characteristics for that camera1
- *
- * @throws NullPointerException if any of the args were {@code null}
- */
- public LegacyFaceDetectMapper(Camera camera, CameraCharacteristics characteristics) {
- mCamera = checkNotNull(camera, "camera must not be null");
- checkNotNull(characteristics, "characteristics must not be null");
-
- mFaceDetectSupported = ArrayUtils.contains(
- characteristics.get(
- CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES),
- STATISTICS_FACE_DETECT_MODE_SIMPLE);
-
- if (!mFaceDetectSupported) {
- return;
- }
-
- mCamera.setFaceDetectionListener(new FaceDetectionListener() {
-
- @Override
- public void onFaceDetection(Camera.Face[] faces, Camera camera) {
- int lengthFaces = faces == null ? 0 : faces.length;
- synchronized (mLock) {
- if (mFaceDetectEnabled) {
- mFaces = faces;
- } else if (lengthFaces > 0) {
- // stopFaceDetectMode could race against the requests, print a debug log
- Log.d(TAG,
- "onFaceDetection - Ignored some incoming faces since" +
- "face detection was disabled");
- }
- }
-
- if (DEBUG) {
- Log.v(TAG, "onFaceDetection - read " + lengthFaces + " faces");
- }
- }
- });
- }
-
- /**
- * Process the face detect mode from the capture request into an api1 face detect toggle.
- *
- * <p>This method should be called after the parameters are {@link LegacyRequestMapper mapped}
- * with the request.</p>
- *
- * <p>Callbacks are processed in the background, and the next call to {@link #mapResultTriggers}
- * will have the latest faces detected as reflected by the camera1 callbacks.</p>
- *
- * <p>None of the arguments will be mutated.</p>
- *
- * @param captureRequest a non-{@code null} request
- * @param parameters a non-{@code null} parameters corresponding to this request (read-only)
- */
- public void processFaceDetectMode(CaptureRequest captureRequest,
- Camera.Parameters parameters) {
- checkNotNull(captureRequest, "captureRequest must not be null");
-
- /*
- * statistics.faceDetectMode
- */
- int fdMode = ParamsUtils.getOrDefault(captureRequest, STATISTICS_FACE_DETECT_MODE,
- STATISTICS_FACE_DETECT_MODE_OFF);
-
- if (fdMode != STATISTICS_FACE_DETECT_MODE_OFF && !mFaceDetectSupported) {
- Log.w(TAG,
- "processFaceDetectMode - Ignoring statistics.faceDetectMode; " +
- "face detection is not available");
- return;
- }
-
- /*
- * control.sceneMode
- */
- int sceneMode = ParamsUtils.getOrDefault(captureRequest, CONTROL_SCENE_MODE,
- CONTROL_SCENE_MODE_DISABLED);
- if (sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY && !mFaceDetectSupported) {
- Log.w(TAG, "processFaceDetectMode - ignoring control.sceneMode == FACE_PRIORITY; " +
- "face detection is not available");
- return;
- }
-
- // Print some warnings out in case the values were wrong
- switch (fdMode) {
- case STATISTICS_FACE_DETECT_MODE_OFF:
- case STATISTICS_FACE_DETECT_MODE_SIMPLE:
- break;
- case STATISTICS_FACE_DETECT_MODE_FULL:
- Log.w(TAG,
- "processFaceDetectMode - statistics.faceDetectMode == FULL unsupported, " +
- "downgrading to SIMPLE");
- break;
- default:
- Log.w(TAG, "processFaceDetectMode - ignoring unknown statistics.faceDetectMode = "
- + fdMode);
- return;
- }
-
- boolean enableFaceDetect = (fdMode != STATISTICS_FACE_DETECT_MODE_OFF)
- || (sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY);
- synchronized (mLock) {
- // Enable/disable face detection if it's changed since last time
- if (enableFaceDetect != mFaceDetectEnabled) {
- if (enableFaceDetect) {
- mCamera.startFaceDetection();
-
- if (DEBUG) {
- Log.v(TAG, "processFaceDetectMode - start face detection");
- }
- } else {
- mCamera.stopFaceDetection();
-
- if (DEBUG) {
- Log.v(TAG, "processFaceDetectMode - stop face detection");
- }
-
- mFaces = null;
- }
-
- mFaceDetectEnabled = enableFaceDetect;
- mFaceDetectScenePriority = sceneMode == CONTROL_SCENE_MODE_FACE_PRIORITY;
- mFaceDetectReporting = fdMode != STATISTICS_FACE_DETECT_MODE_OFF;
- }
- }
- }
-
- /**
- * Update the {@code result} camera metadata map with the new value for the
- * {@code statistics.faces} and {@code statistics.faceDetectMode}.
- *
- * <p>Face detect callbacks are processed in the background, and each call to
- * {@link #mapResultFaces} will have the latest faces as reflected by the camera1 callbacks.</p>
- *
- * <p>If the scene mode was set to {@code FACE_PRIORITY} but face detection is disabled,
- * the camera will still run face detection in the background, but no faces will be reported
- * in the capture result.</p>
- *
- * @param result a non-{@code null} result
- * @param legacyRequest a non-{@code null} request (read-only)
- */
- public void mapResultFaces(CameraMetadataNative result, LegacyRequest legacyRequest) {
- checkNotNull(result, "result must not be null");
- checkNotNull(legacyRequest, "legacyRequest must not be null");
-
- Camera.Face[] faces, previousFaces;
- int fdMode;
- boolean fdScenePriority;
- synchronized (mLock) {
- fdMode = mFaceDetectReporting ?
- STATISTICS_FACE_DETECT_MODE_SIMPLE : STATISTICS_FACE_DETECT_MODE_OFF;
-
- if (mFaceDetectReporting) {
- faces = mFaces;
- } else {
- faces = null;
- }
-
- fdScenePriority = mFaceDetectScenePriority;
-
- previousFaces = mFacesPrev;
- mFacesPrev = faces;
- }
-
- CameraCharacteristics characteristics = legacyRequest.characteristics;
- CaptureRequest request = legacyRequest.captureRequest;
- Size previewSize = legacyRequest.previewSize;
- Camera.Parameters params = legacyRequest.parameters;
-
- Rect activeArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
- ZoomData zoomData = ParameterUtils.convertToLegacyZoom(activeArray,
- request.get(CaptureRequest.SCALER_CROP_REGION),
- request.get(CaptureRequest.CONTROL_ZOOM_RATIO),
- previewSize, params);
-
- List<Face> convertedFaces = new ArrayList<>();
- if (faces != null) {
- for (Camera.Face face : faces) {
- if (face != null) {
- convertedFaces.add(
- ParameterUtils.convertFaceFromLegacy(face, activeArray, zoomData));
- } else {
- Log.w(TAG, "mapResultFaces - read NULL face from camera1 device");
- }
- }
- }
-
- if (DEBUG && previousFaces != faces) { // Log only in verbose and IF the faces changed
- Log.v(TAG, "mapResultFaces - changed to " + ListUtils.listToString(convertedFaces));
- }
-
- result.set(CaptureResult.STATISTICS_FACES, convertedFaces.toArray(new Face[0]));
- result.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, fdMode);
-
- // Override scene mode with FACE_PRIORITY if the request was using FACE_PRIORITY
- if (fdScenePriority) {
- result.set(CaptureResult.CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_FACE_PRIORITY);
- }
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyFocusStateMapper.java b/core/java/android/hardware/camera2/legacy/LegacyFocusStateMapper.java
deleted file mode 100644
index d33c09e..0000000
--- a/core/java/android/hardware/camera2/legacy/LegacyFocusStateMapper.java
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.hardware.Camera;
-import android.hardware.Camera.Parameters;
-import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.utils.ParamsUtils;
-import android.util.Log;
-
-import java.util.Objects;
-
-import static android.hardware.camera2.CaptureRequest.*;
-import static com.android.internal.util.Preconditions.*;
-
-/**
- * Map capture request data into legacy focus state transitions.
- *
- * <p>This object will asynchronously process auto-focus changes, so no interaction
- * with it is necessary beyond reading the current state and updating with the latest trigger.</p>
- */
-@SuppressWarnings("deprecation")
-public class LegacyFocusStateMapper {
- private static String TAG = "LegacyFocusStateMapper";
- private static final boolean DEBUG = false;
-
- private final Camera mCamera;
-
- private int mAfStatePrevious = CONTROL_AF_STATE_INACTIVE;
- private String mAfModePrevious = null;
-
- /** Guard mAfRun and mAfState */
- private final Object mLock = new Object();
- /** Guard access with mLock */
- private int mAfRun = 0;
- /** Guard access with mLock */
- private int mAfState = CONTROL_AF_STATE_INACTIVE;
-
- /**
- * Instantiate a new focus state mapper.
- *
- * @param camera a non-{@code null} camera1 device
- *
- * @throws NullPointerException if any of the args were {@code null}
- */
- public LegacyFocusStateMapper(Camera camera) {
- mCamera = checkNotNull(camera, "camera must not be null");
- }
-
- /**
- * Process the AF triggers from the request as a camera1 autofocus routine.
- *
- * <p>This method should be called after the parameters are {@link LegacyRequestMapper mapped}
- * with the request.</p>
- *
- * <p>Callbacks are processed in the background, and the next call to {@link #mapResultTriggers}
- * will have the latest AF state as reflected by the camera1 callbacks.</p>
- *
- * <p>None of the arguments will be mutated.</p>
- *
- * @param captureRequest a non-{@code null} request
- * @param parameters a non-{@code null} parameters corresponding to this request (read-only)
- */
- public void processRequestTriggers(CaptureRequest captureRequest,
- Camera.Parameters parameters) {
- checkNotNull(captureRequest, "captureRequest must not be null");
-
- /*
- * control.afTrigger
- */
- int afTrigger = ParamsUtils.getOrDefault(captureRequest, CONTROL_AF_TRIGGER,
- CONTROL_AF_TRIGGER_IDLE);
-
- final String afMode = parameters.getFocusMode();
-
- if (!Objects.equals(mAfModePrevious, afMode)) {
- if (DEBUG) {
- Log.v(TAG, "processRequestTriggers - AF mode switched from " + mAfModePrevious +
- " to " + afMode);
- }
-
- // Switching modes always goes back to INACTIVE; ignore callbacks from previous modes
-
- synchronized (mLock) {
- ++mAfRun;
- mAfState = CONTROL_AF_STATE_INACTIVE;
- }
- mCamera.cancelAutoFocus();
- }
-
- mAfModePrevious = afMode;
-
- // Passive AF Scanning
- {
- final int currentAfRun;
-
- synchronized (mLock) {
- currentAfRun = mAfRun;
- }
-
- Camera.AutoFocusMoveCallback afMoveCallback = new Camera.AutoFocusMoveCallback() {
- @Override
- public void onAutoFocusMoving(boolean start, Camera camera) {
- synchronized (mLock) {
- int latestAfRun = mAfRun;
-
- if (DEBUG) {
- Log.v(TAG,
- "onAutoFocusMoving - start " + start + " latest AF run " +
- latestAfRun + ", last AF run " + currentAfRun
- );
- }
-
- if (currentAfRun != latestAfRun) {
- Log.d(TAG,
- "onAutoFocusMoving - ignoring move callbacks from old af run"
- + currentAfRun
- );
- return;
- }
-
- int newAfState = start ?
- CONTROL_AF_STATE_PASSIVE_SCAN :
- CONTROL_AF_STATE_PASSIVE_FOCUSED;
- // We never send CONTROL_AF_STATE_PASSIVE_UNFOCUSED
-
- switch (afMode) {
- case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE:
- case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO:
- break;
- // This callback should never be sent in any other AF mode
- default:
- Log.w(TAG, "onAutoFocus - got unexpected onAutoFocus in mode "
- + afMode);
-
- }
-
- mAfState = newAfState;
- }
- }
- };
-
- // Only set move callback if we can call autofocus.
- switch (afMode) {
- case Parameters.FOCUS_MODE_AUTO:
- case Parameters.FOCUS_MODE_MACRO:
- case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE:
- case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO:
- mCamera.setAutoFocusMoveCallback(afMoveCallback);
- }
- }
-
-
- // AF Locking
- switch (afTrigger) {
- case CONTROL_AF_TRIGGER_START:
-
- int afStateAfterStart;
- switch (afMode) {
- case Parameters.FOCUS_MODE_AUTO:
- case Parameters.FOCUS_MODE_MACRO:
- afStateAfterStart = CONTROL_AF_STATE_ACTIVE_SCAN;
- break;
- case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE:
- case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO:
- afStateAfterStart = CONTROL_AF_STATE_PASSIVE_SCAN;
- break;
- default:
- // EDOF, INFINITY
- afStateAfterStart = CONTROL_AF_STATE_INACTIVE;
- }
-
- final int currentAfRun;
- synchronized (mLock) {
- currentAfRun = ++mAfRun;
- mAfState = afStateAfterStart;
- }
-
- if (DEBUG) {
- Log.v(TAG, "processRequestTriggers - got AF_TRIGGER_START, " +
- "new AF run is " + currentAfRun);
- }
-
- // Avoid calling autofocus unless we are in a state that supports calling this.
- if (afStateAfterStart == CONTROL_AF_STATE_INACTIVE) {
- break;
- }
-
- mCamera.autoFocus(new Camera.AutoFocusCallback() {
- @Override
- public void onAutoFocus(boolean success, Camera camera) {
- synchronized (mLock) {
- int latestAfRun = mAfRun;
-
- if (DEBUG) {
- Log.v(TAG, "onAutoFocus - success " + success + " latest AF run " +
- latestAfRun + ", last AF run " + currentAfRun);
- }
-
- // Ignore old auto-focus results, since another trigger was requested
- if (latestAfRun != currentAfRun) {
- Log.d(TAG, String.format("onAutoFocus - ignoring AF callback " +
- "(old run %d, new run %d)", currentAfRun, latestAfRun));
-
- return;
- }
-
- int newAfState = success ?
- CONTROL_AF_STATE_FOCUSED_LOCKED :
- CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
-
- switch (afMode) {
- case Parameters.FOCUS_MODE_AUTO:
- case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE:
- case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO:
- case Parameters.FOCUS_MODE_MACRO:
- break;
- // This callback should never be sent in any other AF mode
- default:
- Log.w(TAG, "onAutoFocus - got unexpected onAutoFocus in mode "
- + afMode);
-
- }
-
- mAfState = newAfState;
- }
- }
- });
-
- break;
- case CONTROL_AF_TRIGGER_CANCEL:
- synchronized (mLock) {
- int updatedAfRun;
-
- synchronized (mLock) {
- updatedAfRun = ++mAfRun;
- mAfState = CONTROL_AF_STATE_INACTIVE;
- }
-
- mCamera.cancelAutoFocus();
-
- if (DEBUG) {
- Log.v(TAG, "processRequestTriggers - got AF_TRIGGER_CANCEL, " +
- "new AF run is " + updatedAfRun);
- }
- }
-
- break;
- case CONTROL_AF_TRIGGER_IDLE:
- // No action necessary. The callbacks will handle transitions.
- break;
- default:
- Log.w(TAG, "processRequestTriggers - ignoring unknown control.afTrigger = "
- + afTrigger);
- }
- }
-
- /**
- * Update the {@code result} camera metadata map with the new value for the
- * {@code control.afState}.
- *
- * <p>AF callbacks are processed in the background, and each call to {@link #mapResultTriggers}
- * will have the latest AF state as reflected by the camera1 callbacks.</p>
- *
- * @param result a non-{@code null} result
- */
- public void mapResultTriggers(CameraMetadataNative result) {
- checkNotNull(result, "result must not be null");
-
- int newAfState;
- synchronized (mLock) {
- newAfState = mAfState;
- }
-
- if (DEBUG && newAfState != mAfStatePrevious) {
- Log.v(TAG, String.format("mapResultTriggers - afState changed from %s to %s",
- afStateToString(mAfStatePrevious), afStateToString(newAfState)));
- }
-
- result.set(CaptureResult.CONTROL_AF_STATE, newAfState);
-
- mAfStatePrevious = newAfState;
- }
-
- private static String afStateToString(int afState) {
- switch (afState) {
- case CONTROL_AF_STATE_ACTIVE_SCAN:
- return "ACTIVE_SCAN";
- case CONTROL_AF_STATE_FOCUSED_LOCKED:
- return "FOCUSED_LOCKED";
- case CONTROL_AF_STATE_INACTIVE:
- return "INACTIVE";
- case CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
- return "NOT_FOCUSED_LOCKED";
- case CONTROL_AF_STATE_PASSIVE_FOCUSED:
- return "PASSIVE_FOCUSED";
- case CONTROL_AF_STATE_PASSIVE_SCAN:
- return "PASSIVE_SCAN";
- case CONTROL_AF_STATE_PASSIVE_UNFOCUSED:
- return "PASSIVE_UNFOCUSED";
- default :
- return "UNKNOWN(" + afState + ")";
- }
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
deleted file mode 100644
index 362ddfa..0000000
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ /dev/null
@@ -1,1532 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.graphics.ImageFormat;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.hardware.Camera;
-import android.hardware.Camera.CameraInfo;
-import android.hardware.Camera.Parameters;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraMetadata;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.params.MeteringRectangle;
-import android.hardware.camera2.params.StreamConfiguration;
-import android.hardware.camera2.params.StreamConfigurationDuration;
-import android.hardware.camera2.utils.ArrayUtils;
-import android.hardware.camera2.utils.ListUtils;
-import android.hardware.camera2.utils.ParamsUtils;
-import android.util.Log;
-import android.util.Range;
-import android.util.Size;
-import android.util.SizeF;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import static com.android.internal.util.Preconditions.*;
-import static android.hardware.camera2.CameraCharacteristics.*;
-import static android.hardware.camera2.legacy.ParameterUtils.*;
-
-/**
- * Provide legacy-specific implementations of camera2 metadata for legacy devices, such as the
- * camera characteristics.
- */
-@SuppressWarnings("deprecation")
-public class LegacyMetadataMapper {
- private static final String TAG = "LegacyMetadataMapper";
- private static final boolean DEBUG = false;
-
- private static final long NS_PER_MS = 1000000;
-
- // from graphics.h
- public static final int HAL_PIXEL_FORMAT_RGBA_8888 = PixelFormat.RGBA_8888;
- public static final int HAL_PIXEL_FORMAT_BGRA_8888 = 0x5;
- public static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
- public static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
-
- // for metadata
- private static final float LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS = 0.0f;
-
- private static final int REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_RAW = 0; // no raw support
- private static final int REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC = 3; // preview, video, cb
- private static final int REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC_STALL = 1; // 1 jpeg only
- private static final int REQUEST_MAX_NUM_INPUT_STREAMS_COUNT = 0; // no reprocessing
-
- /** Assume 3 HAL1 stages: Exposure, Read-out, Post-Processing */
- private static final int REQUEST_PIPELINE_MAX_DEPTH_HAL1 = 3;
- /** Assume 3 shim stages: Preview input, Split output, Format conversion for output */
- private static final int REQUEST_PIPELINE_MAX_DEPTH_OURS = 3;
- /* TODO: Update above maxDepth values once we do more performance measurements */
-
- // For approximating JPEG stall durations
- private static final long APPROXIMATE_CAPTURE_DELAY_MS = 200; // 200 milliseconds
- private static final long APPROXIMATE_SENSOR_AREA_PX = (1 << 23); // 8 megapixels
- private static final long APPROXIMATE_JPEG_ENCODE_TIME_MS = 600; // 600 milliseconds
-
- static final int UNKNOWN_MODE = -1;
-
- // Maximum difference between a preview size aspect ratio and a jpeg size aspect ratio
- private static final float PREVIEW_ASPECT_RATIO_TOLERANCE = 0.01f;
-
- /*
- * Development hijinks: Lie about not supporting certain capabilities
- *
- * - Unblock some CTS tests from running whose main intent is not the metadata itself
- *
- * TODO: Remove these constants and strip out any code that previously relied on them
- * being set to true.
- */
- static final boolean LIE_ABOUT_AE_STATE = false;
- static final boolean LIE_ABOUT_AE_MAX_REGIONS = false;
- static final boolean LIE_ABOUT_AF = false;
- static final boolean LIE_ABOUT_AF_MAX_REGIONS = false;
- static final boolean LIE_ABOUT_AWB_STATE = false;
- static final boolean LIE_ABOUT_AWB = false;
-
-
- /**
- * Create characteristics for a legacy device by mapping the {@code parameters}
- * and {@code info}
- *
- * @param parameters A non-{@code null} parameters set
- * @param info Camera info with camera facing direction and angle of orientation
- * @param cameraId Current camera Id
- * @param displaySize Device display size
- *
- * @return static camera characteristics for a camera device
- *
- * @throws NullPointerException if any of the args were {@code null}
- */
- public static CameraCharacteristics createCharacteristics(Camera.Parameters parameters,
- CameraInfo info, int cameraId, Size displaySize) {
- checkNotNull(parameters, "parameters must not be null");
- checkNotNull(info, "info must not be null");
-
- String paramStr = parameters.flatten();
- android.hardware.CameraInfo outerInfo = new android.hardware.CameraInfo();
- outerInfo.info = info;
-
- return createCharacteristics(paramStr, outerInfo, cameraId, displaySize);
- }
-
- /**
- * Create characteristics for a legacy device by mapping the {@code parameters}
- * and {@code info}
- *
- * @param parameters A string parseable by {@link Camera.Parameters#unflatten}
- * @param info Camera info with camera facing direction and angle of orientation
- * @param cameraId Current camera id
- * @param displaySize Device display size
- * @return static camera characteristics for a camera device
- *
- * @throws NullPointerException if any of the args were {@code null}
- */
- public static CameraCharacteristics createCharacteristics(String parameters,
- android.hardware.CameraInfo info, int cameraId, Size displaySize) {
- checkNotNull(parameters, "parameters must not be null");
- checkNotNull(info, "info must not be null");
- checkNotNull(info.info, "info.info must not be null");
-
- CameraMetadataNative m = new CameraMetadataNative();
-
- mapCharacteristicsFromInfo(m, info.info);
-
- Camera.Parameters params = Camera.getEmptyParameters();
- params.unflatten(parameters);
- mapCharacteristicsFromParameters(m, params);
-
- if (DEBUG) {
- Log.v(TAG, "createCharacteristics metadata:");
- Log.v(TAG, "--------------------------------------------------- (start)");
- m.dumpToLog();
- Log.v(TAG, "--------------------------------------------------- (end)");
- }
-
- m.setCameraId(cameraId);
- m.setDisplaySize(displaySize);
-
- return new CameraCharacteristics(m);
- }
-
- private static void mapCharacteristicsFromInfo(CameraMetadataNative m, CameraInfo i) {
- m.set(LENS_FACING, i.facing == CameraInfo.CAMERA_FACING_BACK ?
- LENS_FACING_BACK : LENS_FACING_FRONT);
- m.set(SENSOR_ORIENTATION, i.orientation);
- }
-
- private static void mapCharacteristicsFromParameters(CameraMetadataNative m,
- Camera.Parameters p) {
-
- /*
- * colorCorrection.*
- */
- m.set(COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
- new int[] { COLOR_CORRECTION_ABERRATION_MODE_FAST,
- COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY });
- /*
- * control.ae*
- */
- mapControlAe(m, p);
- /*
- * control.af*
- */
- mapControlAf(m, p);
- /*
- * control.awb*
- */
- mapControlAwb(m, p);
- /*
- * control.*
- * - Anything that doesn't have a set of related fields
- */
- mapControlOther(m, p);
- /*
- * lens.*
- */
- mapLens(m, p);
- /*
- * flash.*
- */
- mapFlash(m, p);
- /*
- * jpeg.*
- */
- mapJpeg(m, p);
-
- /*
- * noiseReduction.*
- */
- m.set(NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
- new int[] { NOISE_REDUCTION_MODE_FAST,
- NOISE_REDUCTION_MODE_HIGH_QUALITY});
-
- /*
- * scaler.*
- */
- mapScaler(m, p);
-
- /*
- * sensor.*
- */
- mapSensor(m, p);
-
- /*
- * statistics.*
- */
- mapStatistics(m, p);
-
- /*
- * sync.*
- */
- mapSync(m, p);
-
- /*
- * info.supportedHardwareLevel
- */
- m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
-
- /*
- * scaler.availableStream*, scaler.available*Durations, sensor.info.maxFrameDuration
- */
- mapScalerStreamConfigs(m, p);
-
- // Order matters below: Put this last so that we can read the metadata set previously
-
- /*
- * request.*
- */
- mapRequest(m, p);
-
- }
-
- private static void mapScalerStreamConfigs(CameraMetadataNative m, Camera.Parameters p) {
-
- ArrayList<StreamConfiguration> availableStreamConfigs = new ArrayList<>();
- /*
- * Implementation-defined (preview, recording, etc) -> use camera1 preview sizes
- * YUV_420_888 cpu callbacks -> use camera1 preview sizes
- * Other preview callbacks (CPU) -> use camera1 preview sizes
- * JPEG still capture -> use camera1 still capture sizes
- *
- * Use platform-internal format constants here, since StreamConfigurationMap does the
- * remapping to public format constants.
- */
- List<Camera.Size> previewSizes = p.getSupportedPreviewSizes();
- List<Camera.Size> jpegSizes = p.getSupportedPictureSizes();
- /*
- * Work-around for b/17589233:
- * - Some HALs's largest preview size aspect ratio does not match the largest JPEG size AR
- * - This causes a large amount of problems with focus/metering because it's relative to
- * preview, making the difference between the JPEG and preview viewport inaccessible
- * - This boils down to metering or focusing areas being "arbitrarily" cropped
- * in the capture result.
- * - Work-around the HAL limitations by removing all of the largest preview sizes
- * until we get one with the same aspect ratio as the jpeg size.
- */
- {
- SizeAreaComparator areaComparator = new SizeAreaComparator();
-
- // Sort preview to min->max
- Collections.sort(previewSizes, areaComparator);
-
- Camera.Size maxJpegSize = SizeAreaComparator.findLargestByArea(jpegSizes);
- float jpegAspectRatio = maxJpegSize.width * 1.0f / maxJpegSize.height;
-
- if (DEBUG) {
- Log.v(TAG, String.format("mapScalerStreamConfigs - largest JPEG area %dx%d, AR=%f",
- maxJpegSize.width, maxJpegSize.height, jpegAspectRatio));
- }
-
- // Now remove preview sizes from the end (largest->smallest) until aspect ratio matches
- while (!previewSizes.isEmpty()) {
- int index = previewSizes.size() - 1; // max is always at the end
- Camera.Size size = previewSizes.get(index);
-
- float previewAspectRatio = size.width * 1.0f / size.height;
-
- if (Math.abs(jpegAspectRatio - previewAspectRatio) >=
- PREVIEW_ASPECT_RATIO_TOLERANCE) {
- previewSizes.remove(index); // Assume removing from end is O(1)
-
- if (DEBUG) {
- Log.v(TAG, String.format(
- "mapScalerStreamConfigs - removed preview size %dx%d, AR=%f "
- + "was not the same",
- size.width, size.height, previewAspectRatio));
- }
- } else {
- break;
- }
- }
-
- if (previewSizes.isEmpty()) {
- // Fall-back to the original faulty behavior, but at least work
- Log.w(TAG, "mapScalerStreamConfigs - failed to find any preview size matching " +
- "JPEG aspect ratio " + jpegAspectRatio);
- previewSizes = p.getSupportedPreviewSizes();
- }
-
- // Sort again, this time in descending order max->min
- Collections.sort(previewSizes, Collections.reverseOrder(areaComparator));
- }
-
- appendStreamConfig(availableStreamConfigs,
- HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, previewSizes);
- appendStreamConfig(availableStreamConfigs,
- ImageFormat.YUV_420_888, previewSizes);
- for (int format : p.getSupportedPreviewFormats()) {
- if (ImageFormat.isPublicFormat(format) && format != ImageFormat.NV21) {
- appendStreamConfig(availableStreamConfigs, format, previewSizes);
- } else if (DEBUG) {
- /*
- * Do not add any formats unknown to us
- * (since it would fail runtime checks in StreamConfigurationMap)
- */
- Log.v(TAG,
- String.format("mapStreamConfigs - Skipping format %x", format));
- }
- }
-
- appendStreamConfig(availableStreamConfigs,
- HAL_PIXEL_FORMAT_BLOB, p.getSupportedPictureSizes());
- /*
- * scaler.availableStreamConfigurations
- */
- m.set(SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
- availableStreamConfigs.toArray(new StreamConfiguration[0]));
-
- /*
- * scaler.availableMinFrameDurations
- */
- // No frame durations available
- m.set(SCALER_AVAILABLE_MIN_FRAME_DURATIONS, new StreamConfigurationDuration[0]);
-
- StreamConfigurationDuration[] jpegStalls =
- new StreamConfigurationDuration[jpegSizes.size()];
- int i = 0;
- long longestStallDuration = -1;
- for (Camera.Size s : jpegSizes) {
- long stallDuration = calculateJpegStallDuration(s);
- jpegStalls[i++] = new StreamConfigurationDuration(HAL_PIXEL_FORMAT_BLOB, s.width,
- s.height, stallDuration);
- if (longestStallDuration < stallDuration) {
- longestStallDuration = stallDuration;
- }
- }
- /*
- * scaler.availableStallDurations
- */
- // Set stall durations for jpeg, other formats use default stall duration
- m.set(SCALER_AVAILABLE_STALL_DURATIONS, jpegStalls);
-
- /*
- * sensor.info.maxFrameDuration
- */
- m.set(SENSOR_INFO_MAX_FRAME_DURATION, longestStallDuration);
- }
-
- @SuppressWarnings({"unchecked"})
- private static void mapControlAe(CameraMetadataNative m, Camera.Parameters p) {
- /*
- * control.aeAvailableAntiBandingModes
- */
- List<String> antiBandingModes = p.getSupportedAntibanding();
- if (antiBandingModes != null && antiBandingModes.size() > 0) { // antibanding is optional
- int[] modes = new int[antiBandingModes.size()];
- int j = 0;
- for (String mode : antiBandingModes) {
- int convertedMode = convertAntiBandingMode(mode);
- if (DEBUG && convertedMode == -1) {
- Log.v(TAG, "Antibanding mode " + ((mode == null) ? "NULL" : mode) +
- " not supported, skipping...");
- } else {
- modes[j++] = convertedMode;
- }
- }
- m.set(CONTROL_AE_AVAILABLE_ANTIBANDING_MODES, Arrays.copyOf(modes, j));
- } else {
- m.set(CONTROL_AE_AVAILABLE_ANTIBANDING_MODES, new int[0]);
- }
-
- /*
- * control.aeAvailableTargetFpsRanges
- */
- {
- List<int[]> fpsRanges = p.getSupportedPreviewFpsRange();
- if (fpsRanges == null) {
- throw new AssertionError("Supported FPS ranges cannot be null.");
- }
- int rangesSize = fpsRanges.size();
- if (rangesSize <= 0) {
- throw new AssertionError("At least one FPS range must be supported.");
- }
- Range<Integer>[] ranges = new Range[rangesSize];
- int i = 0;
- for (int[] r : fpsRanges) {
- ranges[i++] = Range.create(
- (int) Math.floor(r[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] / 1000.0),
- (int) Math.ceil(r[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] / 1000.0));
- }
- m.set(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, ranges);
- }
-
- /*
- * control.aeAvailableModes
- */
- {
- List<String> flashModes = p.getSupportedFlashModes();
-
- String[] flashModeStrings = new String[] {
- Camera.Parameters.FLASH_MODE_OFF,
- Camera.Parameters.FLASH_MODE_AUTO,
- Camera.Parameters.FLASH_MODE_ON,
- Camera.Parameters.FLASH_MODE_RED_EYE,
- // Map these manually
- Camera.Parameters.FLASH_MODE_TORCH,
- };
- int[] flashModeInts = new int[] {
- CONTROL_AE_MODE_ON,
- CONTROL_AE_MODE_ON_AUTO_FLASH,
- CONTROL_AE_MODE_ON_ALWAYS_FLASH,
- CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
- };
- int[] aeAvail = ArrayUtils.convertStringListToIntArray(
- flashModes, flashModeStrings, flashModeInts);
-
- // No flash control -> AE is always on
- if (aeAvail == null || aeAvail.length == 0) {
- aeAvail = new int[] {
- CONTROL_AE_MODE_ON
- };
- }
-
- // Note that AE_MODE_OFF is never available.
- m.set(CONTROL_AE_AVAILABLE_MODES, aeAvail);
- }
-
- /*
- * control.aeCompensationRanges
- */
- {
- int min = p.getMinExposureCompensation();
- int max = p.getMaxExposureCompensation();
-
- m.set(CONTROL_AE_COMPENSATION_RANGE, Range.create(min, max));
- }
-
- /*
- * control.aeCompensationStep
- */
- {
- float step = p.getExposureCompensationStep();
-
- m.set(CONTROL_AE_COMPENSATION_STEP, ParamsUtils.createRational(step));
- }
-
- /*
- * control.aeLockAvailable
- */
- {
- boolean aeLockAvailable = p.isAutoExposureLockSupported();
-
- m.set(CONTROL_AE_LOCK_AVAILABLE, aeLockAvailable);
- }
- }
-
-
- @SuppressWarnings({"unchecked"})
- private static void mapControlAf(CameraMetadataNative m, Camera.Parameters p) {
- /*
- * control.afAvailableModes
- */
- {
- List<String> focusModes = p.getSupportedFocusModes();
-
- String[] focusModeStrings = new String[] {
- Camera.Parameters.FOCUS_MODE_AUTO,
- Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
- Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO,
- Camera.Parameters.FOCUS_MODE_EDOF,
- Camera.Parameters.FOCUS_MODE_INFINITY,
- Camera.Parameters.FOCUS_MODE_MACRO,
- Camera.Parameters.FOCUS_MODE_FIXED,
- };
-
- int[] focusModeInts = new int[] {
- CONTROL_AF_MODE_AUTO,
- CONTROL_AF_MODE_CONTINUOUS_PICTURE,
- CONTROL_AF_MODE_CONTINUOUS_VIDEO,
- CONTROL_AF_MODE_EDOF,
- CONTROL_AF_MODE_OFF,
- CONTROL_AF_MODE_MACRO,
- CONTROL_AF_MODE_OFF
- };
-
- List<Integer> afAvail = ArrayUtils.convertStringListToIntList(
- focusModes, focusModeStrings, focusModeInts);
-
- // No AF modes supported? That's unpossible!
- if (afAvail == null || afAvail.size() == 0) {
- Log.w(TAG, "No AF modes supported (HAL bug); defaulting to AF_MODE_OFF only");
- afAvail = new ArrayList<Integer>(/*capacity*/1);
- afAvail.add(CONTROL_AF_MODE_OFF);
- }
-
- m.set(CONTROL_AF_AVAILABLE_MODES, ArrayUtils.toIntArray(afAvail));
-
- if (DEBUG) {
- Log.v(TAG, "mapControlAf - control.afAvailableModes set to " +
- ListUtils.listToString(afAvail));
- }
- }
- }
-
- private static void mapControlAwb(CameraMetadataNative m, Camera.Parameters p) {
- /*
- * control.awbAvailableModes
- */
-
- {
- List<String> wbModes = p.getSupportedWhiteBalance();
-
- String[] wbModeStrings = new String[] {
- Camera.Parameters.WHITE_BALANCE_AUTO ,
- Camera.Parameters.WHITE_BALANCE_INCANDESCENT ,
- Camera.Parameters.WHITE_BALANCE_FLUORESCENT ,
- Camera.Parameters.WHITE_BALANCE_WARM_FLUORESCENT ,
- Camera.Parameters.WHITE_BALANCE_DAYLIGHT ,
- Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT ,
- Camera.Parameters.WHITE_BALANCE_TWILIGHT ,
- Camera.Parameters.WHITE_BALANCE_SHADE ,
- };
-
- int[] wbModeInts = new int[] {
- CONTROL_AWB_MODE_AUTO,
- CONTROL_AWB_MODE_INCANDESCENT ,
- CONTROL_AWB_MODE_FLUORESCENT ,
- CONTROL_AWB_MODE_WARM_FLUORESCENT ,
- CONTROL_AWB_MODE_DAYLIGHT ,
- CONTROL_AWB_MODE_CLOUDY_DAYLIGHT ,
- CONTROL_AWB_MODE_TWILIGHT ,
- CONTROL_AWB_MODE_SHADE ,
- // Note that CONTROL_AWB_MODE_OFF is unsupported
- };
-
- List<Integer> awbAvail = ArrayUtils.convertStringListToIntList(
- wbModes, wbModeStrings, wbModeInts);
-
- // No AWB modes supported? That's unpossible!
- if (awbAvail == null || awbAvail.size() == 0) {
- Log.w(TAG, "No AWB modes supported (HAL bug); defaulting to AWB_MODE_AUTO only");
- awbAvail = new ArrayList<Integer>(/*capacity*/1);
- awbAvail.add(CONTROL_AWB_MODE_AUTO);
- }
-
- m.set(CONTROL_AWB_AVAILABLE_MODES, ArrayUtils.toIntArray(awbAvail));
-
- if (DEBUG) {
- Log.v(TAG, "mapControlAwb - control.awbAvailableModes set to " +
- ListUtils.listToString(awbAvail));
- }
-
-
- /*
- * control.awbLockAvailable
- */
- {
- boolean awbLockAvailable = p.isAutoWhiteBalanceLockSupported();
-
- m.set(CONTROL_AWB_LOCK_AVAILABLE, awbLockAvailable);
- }
- }
- }
-
- private static void mapControlOther(CameraMetadataNative m, Camera.Parameters p) {
- /*
- * android.control.availableVideoStabilizationModes
- */
- {
- int stabModes[] = p.isVideoStabilizationSupported() ?
- new int[] { CONTROL_VIDEO_STABILIZATION_MODE_OFF,
- CONTROL_VIDEO_STABILIZATION_MODE_ON } :
- new int[] { CONTROL_VIDEO_STABILIZATION_MODE_OFF };
-
- m.set(CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES, stabModes);
- }
-
- /*
- * android.control.maxRegions
- */
- final int AE = 0, AWB = 1, AF = 2;
-
- int[] maxRegions = new int[3];
- maxRegions[AE] = p.getMaxNumMeteringAreas();
- maxRegions[AWB] = 0; // AWB regions not supported in API1
- maxRegions[AF] = p.getMaxNumFocusAreas();
-
- if (LIE_ABOUT_AE_MAX_REGIONS) {
- maxRegions[AE] = 0;
- }
- if (LIE_ABOUT_AF_MAX_REGIONS) {
- maxRegions[AF] = 0;
- }
-
- m.set(CONTROL_MAX_REGIONS, maxRegions);
-
- /*
- * android.control.availableEffects
- */
- List<String> effectModes = p.getSupportedColorEffects();
- int[] supportedEffectModes = (effectModes == null) ? new int[0] :
- ArrayUtils.convertStringListToIntArray(effectModes, sLegacyEffectMode,
- sEffectModes);
- m.set(CONTROL_AVAILABLE_EFFECTS, supportedEffectModes);
-
- /*
- * android.control.availableSceneModes
- */
- int maxNumDetectedFaces = p.getMaxNumDetectedFaces();
- List<String> sceneModes = p.getSupportedSceneModes();
- List<Integer> supportedSceneModes =
- ArrayUtils.convertStringListToIntList(sceneModes, sLegacySceneModes, sSceneModes);
-
- // Special case where the only scene mode listed is AUTO => no scene mode
- if (sceneModes != null && sceneModes.size() == 1 &&
- sceneModes.get(0).equals(Parameters.SCENE_MODE_AUTO)) {
- supportedSceneModes = null;
- }
-
- boolean sceneModeSupported = true;
- if (supportedSceneModes == null && maxNumDetectedFaces == 0) {
- sceneModeSupported = false;
- }
-
- if (sceneModeSupported) {
- if (supportedSceneModes == null) {
- supportedSceneModes = new ArrayList<Integer>();
- }
- if (maxNumDetectedFaces > 0) { // always supports FACE_PRIORITY when face detecting
- supportedSceneModes.add(CONTROL_SCENE_MODE_FACE_PRIORITY);
- }
- // Remove all DISABLED occurrences
- if (supportedSceneModes.contains(CONTROL_SCENE_MODE_DISABLED)) {
- while(supportedSceneModes.remove(new Integer(CONTROL_SCENE_MODE_DISABLED))) {}
- }
- m.set(CONTROL_AVAILABLE_SCENE_MODES, ArrayUtils.toIntArray(supportedSceneModes));
- } else {
- m.set(CONTROL_AVAILABLE_SCENE_MODES, new int[] {CONTROL_SCENE_MODE_DISABLED});
- }
-
- /*
- * android.control.availableModes
- */
- m.set(CONTROL_AVAILABLE_MODES, sceneModeSupported ?
- new int[] { CONTROL_MODE_AUTO, CONTROL_MODE_USE_SCENE_MODE } :
- new int[] { CONTROL_MODE_AUTO });
- }
-
- private static void mapLens(CameraMetadataNative m, Camera.Parameters p) {
- /*
- * We can tell if the lens is fixed focus;
- * but if it's not, we can't tell the minimum focus distance, so leave it null then.
- */
- if (DEBUG) {
- Log.v(TAG, "mapLens - focus-mode='" + p.getFocusMode() + "'");
- }
-
- if (Camera.Parameters.FOCUS_MODE_FIXED.equals(p.getFocusMode())) {
- /*
- * lens.info.minimumFocusDistance
- */
- m.set(LENS_INFO_MINIMUM_FOCUS_DISTANCE, LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS);
-
- if (DEBUG) {
- Log.v(TAG, "mapLens - lens.info.minimumFocusDistance = 0");
- }
- } else {
- if (DEBUG) {
- Log.v(TAG, "mapLens - lens.info.minimumFocusDistance is unknown");
- }
- }
-
- float[] focalLengths = new float[] { p.getFocalLength() };
- m.set(LENS_INFO_AVAILABLE_FOCAL_LENGTHS, focalLengths);
- }
-
- private static void mapFlash(CameraMetadataNative m, Camera.Parameters p) {
- boolean flashAvailable = false;
- List<String> supportedFlashModes = p.getSupportedFlashModes();
-
- if (supportedFlashModes != null) {
- // If only 'OFF' is available, we don't really have flash support
- flashAvailable = !ListUtils.listElementsEqualTo(
- supportedFlashModes, Camera.Parameters.FLASH_MODE_OFF);
- }
-
- /*
- * flash.info.available
- */
- m.set(FLASH_INFO_AVAILABLE, flashAvailable);
- }
-
- private static void mapJpeg(CameraMetadataNative m, Camera.Parameters p) {
- List<Camera.Size> thumbnailSizes = p.getSupportedJpegThumbnailSizes();
-
- if (thumbnailSizes != null) {
- Size[] sizes = convertSizeListToArray(thumbnailSizes);
- Arrays.sort(sizes, new android.hardware.camera2.utils.SizeAreaComparator());
- m.set(JPEG_AVAILABLE_THUMBNAIL_SIZES, sizes);
- }
- }
-
- private static void mapRequest(CameraMetadataNative m, Parameters p) {
- /*
- * request.availableCapabilities
- */
- int[] capabilities = { REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE };
- m.set(REQUEST_AVAILABLE_CAPABILITIES, capabilities);
-
- /*
- * request.availableCharacteristicsKeys
- */
- {
- // TODO: check if the underlying key is supported before listing a key as available
-
- // Note: We only list public keys. Native HALs should list ALL keys regardless of visibility.
-
- Key<?> availableKeys[] = new Key<?>[] {
- CameraCharacteristics.COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES ,
- CameraCharacteristics.CONTROL_AE_AVAILABLE_ANTIBANDING_MODES ,
- CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES ,
- CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES ,
- CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE ,
- CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP ,
- CameraCharacteristics.CONTROL_AE_LOCK_AVAILABLE ,
- CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES ,
- CameraCharacteristics.CONTROL_AVAILABLE_EFFECTS ,
- CameraCharacteristics.CONTROL_AVAILABLE_MODES ,
- CameraCharacteristics.CONTROL_AVAILABLE_SCENE_MODES ,
- CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES ,
- CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES ,
- CameraCharacteristics.CONTROL_AWB_LOCK_AVAILABLE ,
- CameraCharacteristics.CONTROL_MAX_REGIONS ,
- CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE ,
- CameraCharacteristics.FLASH_INFO_AVAILABLE ,
- CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL ,
- CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES ,
- CameraCharacteristics.LENS_FACING ,
- CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS ,
- CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES ,
- CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES ,
- CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_STREAMS ,
- CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT ,
- CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH ,
- CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM ,
-// CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP ,
- CameraCharacteristics.SCALER_CROPPING_TYPE ,
- CameraCharacteristics.SENSOR_AVAILABLE_TEST_PATTERN_MODES ,
- CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE ,
- CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE ,
- CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE ,
- CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE ,
- CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE ,
- CameraCharacteristics.SENSOR_ORIENTATION ,
- CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES ,
- CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT ,
- CameraCharacteristics.SYNC_MAX_LATENCY ,
- };
- List<Key<?>> characteristicsKeys = new ArrayList<>(Arrays.asList(availableKeys));
-
- /*
- * Add the conditional keys
- */
- if (m.get(LENS_INFO_MINIMUM_FOCUS_DISTANCE) != null) {
- characteristicsKeys.add(LENS_INFO_MINIMUM_FOCUS_DISTANCE);
- }
-
- m.set(REQUEST_AVAILABLE_CHARACTERISTICS_KEYS,
- getTagsForKeys(characteristicsKeys.toArray(new Key<?>[0])));
- }
-
- /*
- * request.availableRequestKeys
- */
- {
- CaptureRequest.Key<?> defaultAvailableKeys[] = new CaptureRequest.Key<?>[] {
- CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE,
- CaptureRequest.CONTROL_AE_ANTIBANDING_MODE,
- CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION,
- CaptureRequest.CONTROL_AE_LOCK,
- CaptureRequest.CONTROL_AE_MODE,
- CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
- CaptureRequest.CONTROL_AF_MODE,
- CaptureRequest.CONTROL_AF_TRIGGER,
- CaptureRequest.CONTROL_AWB_LOCK,
- CaptureRequest.CONTROL_AWB_MODE,
- CaptureRequest.CONTROL_CAPTURE_INTENT,
- CaptureRequest.CONTROL_EFFECT_MODE,
- CaptureRequest.CONTROL_MODE,
- CaptureRequest.CONTROL_SCENE_MODE,
- CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
- CaptureRequest.CONTROL_ZOOM_RATIO,
- CaptureRequest.FLASH_MODE,
- CaptureRequest.JPEG_GPS_COORDINATES,
- CaptureRequest.JPEG_GPS_PROCESSING_METHOD,
- CaptureRequest.JPEG_GPS_TIMESTAMP,
- CaptureRequest.JPEG_ORIENTATION,
- CaptureRequest.JPEG_QUALITY,
- CaptureRequest.JPEG_THUMBNAIL_QUALITY,
- CaptureRequest.JPEG_THUMBNAIL_SIZE,
- CaptureRequest.LENS_FOCAL_LENGTH,
- CaptureRequest.NOISE_REDUCTION_MODE,
- CaptureRequest.SCALER_CROP_REGION,
- CaptureRequest.STATISTICS_FACE_DETECT_MODE,
- };
- ArrayList<CaptureRequest.Key<?>> availableKeys =
- new ArrayList<CaptureRequest.Key<?>>(Arrays.asList(defaultAvailableKeys));
-
- if (p.getMaxNumMeteringAreas() > 0) {
- availableKeys.add(CaptureRequest.CONTROL_AE_REGIONS);
- }
- if (p.getMaxNumFocusAreas() > 0) {
- availableKeys.add(CaptureRequest.CONTROL_AF_REGIONS);
- }
-
- CaptureRequest.Key<?> availableRequestKeys[] =
- new CaptureRequest.Key<?>[availableKeys.size()];
- availableKeys.toArray(availableRequestKeys);
- m.set(REQUEST_AVAILABLE_REQUEST_KEYS, getTagsForKeys(availableRequestKeys));
- }
-
- /*
- * request.availableResultKeys
- */
- {
- CaptureResult.Key<?> defaultAvailableKeys[] = new CaptureResult.Key<?>[] {
- CaptureResult.COLOR_CORRECTION_ABERRATION_MODE ,
- CaptureResult.CONTROL_AE_ANTIBANDING_MODE ,
- CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION ,
- CaptureResult.CONTROL_AE_LOCK ,
- CaptureResult.CONTROL_AE_MODE ,
- CaptureResult.CONTROL_AF_MODE ,
- CaptureResult.CONTROL_AF_STATE ,
- CaptureResult.CONTROL_AWB_MODE ,
- CaptureResult.CONTROL_AWB_LOCK ,
- CaptureResult.CONTROL_MODE ,
- CaptureResult.CONTROL_ZOOM_RATIO ,
- CaptureResult.FLASH_MODE ,
- CaptureResult.JPEG_GPS_COORDINATES ,
- CaptureResult.JPEG_GPS_PROCESSING_METHOD ,
- CaptureResult.JPEG_GPS_TIMESTAMP ,
- CaptureResult.JPEG_ORIENTATION ,
- CaptureResult.JPEG_QUALITY ,
- CaptureResult.JPEG_THUMBNAIL_QUALITY ,
- CaptureResult.LENS_FOCAL_LENGTH ,
- CaptureResult.NOISE_REDUCTION_MODE ,
- CaptureResult.REQUEST_PIPELINE_DEPTH ,
- CaptureResult.SCALER_CROP_REGION ,
- CaptureResult.SENSOR_TIMESTAMP ,
- CaptureResult.STATISTICS_FACE_DETECT_MODE ,
-// CaptureResult.STATISTICS_FACES ,
- };
- List<CaptureResult.Key<?>> availableKeys =
- new ArrayList<CaptureResult.Key<?>>(Arrays.asList(defaultAvailableKeys));
-
- if (p.getMaxNumMeteringAreas() > 0) {
- availableKeys.add(CaptureResult.CONTROL_AE_REGIONS);
- }
- if (p.getMaxNumFocusAreas() > 0) {
- availableKeys.add(CaptureResult.CONTROL_AF_REGIONS);
- }
-
- CaptureResult.Key<?> availableResultKeys[] =
- new CaptureResult.Key<?>[availableKeys.size()];
- availableKeys.toArray(availableResultKeys);
- m.set(REQUEST_AVAILABLE_RESULT_KEYS, getTagsForKeys(availableResultKeys));
- }
-
- /*
- * request.maxNumOutputStreams
- */
- int[] outputStreams = {
- /* RAW */
- REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_RAW,
- /* Processed & Not-Stalling */
- REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC,
- /* Processed & Stalling */
- REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC_STALL,
- };
- m.set(REQUEST_MAX_NUM_OUTPUT_STREAMS, outputStreams);
-
- /*
- * request.maxNumInputStreams
- */
- m.set(REQUEST_MAX_NUM_INPUT_STREAMS, REQUEST_MAX_NUM_INPUT_STREAMS_COUNT);
-
- /*
- * request.partialResultCount
- */
- m.set(REQUEST_PARTIAL_RESULT_COUNT, 1); // No partial results supported
-
- /*
- * request.pipelineMaxDepth
- */
- m.set(REQUEST_PIPELINE_MAX_DEPTH,
- (byte)(REQUEST_PIPELINE_MAX_DEPTH_HAL1 + REQUEST_PIPELINE_MAX_DEPTH_OURS));
- }
-
- private static void mapScaler(CameraMetadataNative m, Parameters p) {
- /*
- * control.zoomRatioRange
- */
- Range<Float> zoomRatioRange = new Range<Float>(1.0f, ParameterUtils.getMaxZoomRatio(p));
- m.set(CONTROL_ZOOM_RATIO_RANGE, zoomRatioRange);
-
- /*
- * scaler.availableMaxDigitalZoom
- */
- m.set(SCALER_AVAILABLE_MAX_DIGITAL_ZOOM, ParameterUtils.getMaxZoomRatio(p));
-
- /*
- * scaler.croppingType = CENTER_ONLY
- */
- m.set(SCALER_CROPPING_TYPE, SCALER_CROPPING_TYPE_CENTER_ONLY);
- }
-
- private static void mapSensor(CameraMetadataNative m, Parameters p) {
- // Use the largest jpeg size (by area) for both active array and pixel array
- Size largestJpegSize = getLargestSupportedJpegSizeByArea(p);
- /*
- * sensor.info.activeArraySize, and preCorrectionActiveArraySize
- */
- {
- Rect activeArrayRect = ParamsUtils.createRect(largestJpegSize);
- m.set(SENSOR_INFO_ACTIVE_ARRAY_SIZE, activeArrayRect);
- m.set(SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, activeArrayRect);
- }
-
- /*
- * sensor.availableTestPatternModes
- */
- {
- // Only "OFF" test pattern mode is available
- m.set(SENSOR_AVAILABLE_TEST_PATTERN_MODES, new int[] { SENSOR_TEST_PATTERN_MODE_OFF });
- }
-
- /*
- * sensor.info.pixelArraySize
- */
- m.set(SENSOR_INFO_PIXEL_ARRAY_SIZE, largestJpegSize);
-
- /*
- * sensor.info.physicalSize
- */
- {
- /*
- * Assume focal length is at infinity focus and that the lens is rectilinear.
- */
- float focalLength = p.getFocalLength(); // in mm
- double angleHor = p.getHorizontalViewAngle() * Math.PI / 180; // to radians
- double angleVer = p.getVerticalViewAngle() * Math.PI / 180; // to radians
-
- float height = (float)Math.abs(2 * focalLength * Math.tan(angleVer / 2));
- float width = (float)Math.abs(2 * focalLength * Math.tan(angleHor / 2));
-
- m.set(SENSOR_INFO_PHYSICAL_SIZE, new SizeF(width, height)); // in mm
- }
-
- /*
- * sensor.info.timestampSource
- */
- {
- m.set(SENSOR_INFO_TIMESTAMP_SOURCE, SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN);
- }
- }
-
- private static void mapStatistics(CameraMetadataNative m, Parameters p) {
- /*
- * statistics.info.availableFaceDetectModes
- */
- int[] fdModes;
-
- if (p.getMaxNumDetectedFaces() > 0) {
- fdModes = new int[] {
- STATISTICS_FACE_DETECT_MODE_OFF,
- STATISTICS_FACE_DETECT_MODE_SIMPLE
- // FULL is never-listed, since we have no way to query it statically
- };
- } else {
- fdModes = new int[] {
- STATISTICS_FACE_DETECT_MODE_OFF
- };
- }
- m.set(STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES, fdModes);
-
- /*
- * statistics.info.maxFaceCount
- */
- m.set(STATISTICS_INFO_MAX_FACE_COUNT, p.getMaxNumDetectedFaces());
- }
-
- private static void mapSync(CameraMetadataNative m, Parameters p) {
- /*
- * sync.maxLatency
- */
- m.set(SYNC_MAX_LATENCY, SYNC_MAX_LATENCY_UNKNOWN);
- }
-
- private static void appendStreamConfig(
- ArrayList<StreamConfiguration> configs, int format, List<Camera.Size> sizes) {
- for (Camera.Size size : sizes) {
- StreamConfiguration config =
- new StreamConfiguration(format, size.width, size.height, /*input*/false);
- configs.add(config);
- }
- }
-
- private final static String[] sLegacySceneModes = {
- Parameters.SCENE_MODE_AUTO,
- Parameters.SCENE_MODE_ACTION,
- Parameters.SCENE_MODE_PORTRAIT,
- Parameters.SCENE_MODE_LANDSCAPE,
- Parameters.SCENE_MODE_NIGHT,
- Parameters.SCENE_MODE_NIGHT_PORTRAIT,
- Parameters.SCENE_MODE_THEATRE,
- Parameters.SCENE_MODE_BEACH,
- Parameters.SCENE_MODE_SNOW,
- Parameters.SCENE_MODE_SUNSET,
- Parameters.SCENE_MODE_STEADYPHOTO,
- Parameters.SCENE_MODE_FIREWORKS,
- Parameters.SCENE_MODE_SPORTS,
- Parameters.SCENE_MODE_PARTY,
- Parameters.SCENE_MODE_CANDLELIGHT,
- Parameters.SCENE_MODE_BARCODE,
- Parameters.SCENE_MODE_HDR,
- };
-
- private final static int[] sSceneModes = {
- CameraCharacteristics.CONTROL_SCENE_MODE_DISABLED,
- CameraCharacteristics.CONTROL_SCENE_MODE_ACTION,
- CameraCharacteristics.CONTROL_SCENE_MODE_PORTRAIT,
- CameraCharacteristics.CONTROL_SCENE_MODE_LANDSCAPE,
- CameraCharacteristics.CONTROL_SCENE_MODE_NIGHT,
- CameraCharacteristics.CONTROL_SCENE_MODE_NIGHT_PORTRAIT,
- CameraCharacteristics.CONTROL_SCENE_MODE_THEATRE,
- CameraCharacteristics.CONTROL_SCENE_MODE_BEACH,
- CameraCharacteristics.CONTROL_SCENE_MODE_SNOW,
- CameraCharacteristics.CONTROL_SCENE_MODE_SUNSET,
- CameraCharacteristics.CONTROL_SCENE_MODE_STEADYPHOTO,
- CameraCharacteristics.CONTROL_SCENE_MODE_FIREWORKS,
- CameraCharacteristics.CONTROL_SCENE_MODE_SPORTS,
- CameraCharacteristics.CONTROL_SCENE_MODE_PARTY,
- CameraCharacteristics.CONTROL_SCENE_MODE_CANDLELIGHT,
- CameraCharacteristics.CONTROL_SCENE_MODE_BARCODE,
- CameraCharacteristics.CONTROL_SCENE_MODE_HDR,
- };
-
- static int convertSceneModeFromLegacy(String mode) {
- if (mode == null) {
- return CameraCharacteristics.CONTROL_SCENE_MODE_DISABLED;
- }
- int index = ArrayUtils.getArrayIndex(sLegacySceneModes, mode);
- if (index < 0) {
- return UNKNOWN_MODE;
- }
- return sSceneModes[index];
- }
-
- static String convertSceneModeToLegacy(int mode) {
- if (mode == CONTROL_SCENE_MODE_FACE_PRIORITY) {
- // OK: Let LegacyFaceDetectMapper handle turning face detection on/off
- return Parameters.SCENE_MODE_AUTO;
- }
-
- int index = ArrayUtils.getArrayIndex(sSceneModes, mode);
- if (index < 0) {
- return null;
- }
- return sLegacySceneModes[index];
- }
-
- private final static String[] sLegacyEffectMode = {
- Parameters.EFFECT_NONE,
- Parameters.EFFECT_MONO,
- Parameters.EFFECT_NEGATIVE,
- Parameters.EFFECT_SOLARIZE,
- Parameters.EFFECT_SEPIA,
- Parameters.EFFECT_POSTERIZE,
- Parameters.EFFECT_WHITEBOARD,
- Parameters.EFFECT_BLACKBOARD,
- Parameters.EFFECT_AQUA,
- };
-
- private final static int[] sEffectModes = {
- CameraCharacteristics.CONTROL_EFFECT_MODE_OFF,
- CameraCharacteristics.CONTROL_EFFECT_MODE_MONO,
- CameraCharacteristics.CONTROL_EFFECT_MODE_NEGATIVE,
- CameraCharacteristics.CONTROL_EFFECT_MODE_SOLARIZE,
- CameraCharacteristics.CONTROL_EFFECT_MODE_SEPIA,
- CameraCharacteristics.CONTROL_EFFECT_MODE_POSTERIZE,
- CameraCharacteristics.CONTROL_EFFECT_MODE_WHITEBOARD,
- CameraCharacteristics.CONTROL_EFFECT_MODE_BLACKBOARD,
- CameraCharacteristics.CONTROL_EFFECT_MODE_AQUA,
- };
-
- static int convertEffectModeFromLegacy(String mode) {
- if (mode == null) {
- return CameraCharacteristics.CONTROL_EFFECT_MODE_OFF;
- }
- int index = ArrayUtils.getArrayIndex(sLegacyEffectMode, mode);
- if (index < 0) {
- return UNKNOWN_MODE;
- }
- return sEffectModes[index];
- }
-
- static String convertEffectModeToLegacy(int mode) {
- int index = ArrayUtils.getArrayIndex(sEffectModes, mode);
- if (index < 0) {
- return null;
- }
- return sLegacyEffectMode[index];
- }
-
- /**
- * Convert the ae antibanding mode from api1 into api2.
- *
- * @param mode the api1 mode, {@code null} is allowed and will return {@code -1}.
- *
- * @return The api2 value, or {@code -1} by default if conversion failed
- */
- private static int convertAntiBandingMode(String mode) {
- if (mode == null) {
- return -1;
- }
-
- switch (mode) {
- case Camera.Parameters.ANTIBANDING_OFF: {
- return CONTROL_AE_ANTIBANDING_MODE_OFF;
- }
- case Camera.Parameters.ANTIBANDING_50HZ: {
- return CONTROL_AE_ANTIBANDING_MODE_50HZ;
- }
- case Camera.Parameters.ANTIBANDING_60HZ: {
- return CONTROL_AE_ANTIBANDING_MODE_60HZ;
- }
- case Camera.Parameters.ANTIBANDING_AUTO: {
- return CONTROL_AE_ANTIBANDING_MODE_AUTO;
- }
- default: {
- Log.w(TAG, "convertAntiBandingMode - Unknown antibanding mode " + mode);
- return -1;
- }
- }
- }
-
- /**
- * Convert the ae antibanding mode from api1 into api2.
- *
- * @param mode the api1 mode, {@code null} is allowed and will return {@code MODE_OFF}.
- *
- * @return The api2 value, or {@code MODE_OFF} by default if conversion failed
- */
- static int convertAntiBandingModeOrDefault(String mode) {
- int antiBandingMode = convertAntiBandingMode(mode);
- if (antiBandingMode == -1) {
- return CONTROL_AE_ANTIBANDING_MODE_OFF;
- }
-
- return antiBandingMode;
- }
-
- private static int[] convertAeFpsRangeToLegacy(Range<Integer> fpsRange) {
- int[] legacyFps = new int[2];
- legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] = fpsRange.getLower();
- legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] = fpsRange.getUpper();
- return legacyFps;
- }
-
- /**
- * Return the stall duration for a given output jpeg size in nanoseconds.
- *
- * <p>An 8mp image is chosen to have a stall duration of 0.8 seconds.</p>
- */
- private static long calculateJpegStallDuration(Camera.Size size) {
- long baseDuration = APPROXIMATE_CAPTURE_DELAY_MS * NS_PER_MS; // 200ms for capture
- long area = size.width * (long) size.height;
- long stallPerArea = APPROXIMATE_JPEG_ENCODE_TIME_MS * NS_PER_MS /
- APPROXIMATE_SENSOR_AREA_PX; // 600ms stall for 8mp
- return baseDuration + area * stallPerArea;
- }
-
- /**
- * Set the legacy parameters using the {@link LegacyRequest legacy request}.
- *
- * <p>The legacy request's parameters are changed as a side effect of calling this
- * method.</p>
- *
- * @param request a non-{@code null} legacy request
- */
- public static void convertRequestMetadata(LegacyRequest request) {
- LegacyRequestMapper.convertRequestMetadata(request);
- }
-
- private static final int[] sAllowedTemplates = {
- CameraDevice.TEMPLATE_PREVIEW,
- CameraDevice.TEMPLATE_STILL_CAPTURE,
- CameraDevice.TEMPLATE_RECORD,
- // Disallowed templates in legacy mode:
- // CameraDevice.TEMPLATE_VIDEO_SNAPSHOT,
- // CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
- // CameraDevice.TEMPLATE_MANUAL
- };
-
- /**
- * Create a request template
- *
- * @param c a non-{@code null} camera characteristics for this camera
- * @param templateId a non-negative template ID
- *
- * @return a non-{@code null} request template
- *
- * @throws IllegalArgumentException if {@code templateId} was invalid
- *
- * @see android.hardware.camera2.CameraDevice#TEMPLATE_MANUAL
- */
- public static CameraMetadataNative createRequestTemplate(
- CameraCharacteristics c, int templateId) {
- if (!ArrayUtils.contains(sAllowedTemplates, templateId)) {
- throw new IllegalArgumentException("templateId out of range");
- }
-
- CameraMetadataNative m = new CameraMetadataNative();
-
- /*
- * NOTE: If adding new code here and it needs to query the static info,
- * query the camera characteristics, so we can reuse this for api2 code later
- * to create our own templates in the framework
- */
-
- /*
- * control.*
- */
-
- // control.awbMode
- m.set(CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_AUTO);
- // AWB is always unconditionally available in API1 devices
-
- // control.aeAntibandingMode
- m.set(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE, CONTROL_AE_ANTIBANDING_MODE_AUTO);
-
- // control.aeExposureCompensation
- m.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 0);
-
- // control.aeLock
- m.set(CaptureRequest.CONTROL_AE_LOCK, false);
-
- // control.aePrecaptureTrigger
- m.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
-
- // control.afTrigger
- m.set(CaptureRequest.CONTROL_AF_TRIGGER, CONTROL_AF_TRIGGER_IDLE);
-
- // control.awbMode
- m.set(CaptureRequest.CONTROL_AWB_MODE, CONTROL_AWB_MODE_AUTO);
-
- // control.awbLock
- m.set(CaptureRequest.CONTROL_AWB_LOCK, false);
-
- // control.aeRegions, control.awbRegions, control.afRegions
- {
- Rect activeArray = c.get(SENSOR_INFO_ACTIVE_ARRAY_SIZE);
- MeteringRectangle[] activeRegions = new MeteringRectangle[] {
- new MeteringRectangle(/*x*/0, /*y*/0, /*width*/activeArray.width() - 1,
- /*height*/activeArray.height() - 1,/*weight*/0)};
- m.set(CaptureRequest.CONTROL_AE_REGIONS, activeRegions);
- m.set(CaptureRequest.CONTROL_AWB_REGIONS, activeRegions);
- m.set(CaptureRequest.CONTROL_AF_REGIONS, activeRegions);
- }
-
- // control.captureIntent
- {
- int captureIntent;
- switch (templateId) {
- case CameraDevice.TEMPLATE_PREVIEW:
- captureIntent = CONTROL_CAPTURE_INTENT_PREVIEW;
- break;
- case CameraDevice.TEMPLATE_STILL_CAPTURE:
- captureIntent = CONTROL_CAPTURE_INTENT_STILL_CAPTURE;
- break;
- case CameraDevice.TEMPLATE_RECORD:
- captureIntent = CONTROL_CAPTURE_INTENT_VIDEO_RECORD;
- break;
- default:
- // Can't get anything else since it's guarded by the IAE check
- throw new AssertionError("Impossible; keep in sync with sAllowedTemplates");
- }
- m.set(CaptureRequest.CONTROL_CAPTURE_INTENT, captureIntent);
- }
-
- // control.aeMode
- m.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON);
- // AE is always unconditionally available in API1 devices
-
- // control.mode
- m.set(CaptureRequest.CONTROL_MODE, CONTROL_MODE_AUTO);
-
- // control.afMode
- {
- Float minimumFocusDistance = c.get(LENS_INFO_MINIMUM_FOCUS_DISTANCE);
-
- int afMode;
- if (minimumFocusDistance != null &&
- minimumFocusDistance == LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS) {
- // Cannot control auto-focus with fixed-focus cameras
- afMode = CameraMetadata.CONTROL_AF_MODE_OFF;
- } else {
- // If a minimum focus distance is reported; the camera must have AF
- afMode = CameraMetadata.CONTROL_AF_MODE_AUTO;
-
- if (templateId == CameraDevice.TEMPLATE_RECORD ||
- templateId == CameraDevice.TEMPLATE_VIDEO_SNAPSHOT) {
- if (ArrayUtils.contains(c.get(CONTROL_AF_AVAILABLE_MODES),
- CONTROL_AF_MODE_CONTINUOUS_VIDEO)) {
- afMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO;
- }
- } else if (templateId == CameraDevice.TEMPLATE_PREVIEW ||
- templateId == CameraDevice.TEMPLATE_STILL_CAPTURE) {
- if (ArrayUtils.contains(c.get(CONTROL_AF_AVAILABLE_MODES),
- CONTROL_AF_MODE_CONTINUOUS_PICTURE)) {
- afMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
- }
- }
- }
-
- if (DEBUG) {
- Log.v(TAG, "createRequestTemplate (templateId=" + templateId + ")," +
- " afMode=" + afMode + ", minimumFocusDistance=" + minimumFocusDistance);
- }
-
- m.set(CaptureRequest.CONTROL_AF_MODE, afMode);
- }
-
- {
- // control.aeTargetFpsRange
- Range<Integer>[] availableFpsRange = c.
- get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
-
- // Pick FPS range with highest max value, tiebreak on higher min value
- Range<Integer> bestRange = availableFpsRange[0];
- for (Range<Integer> r : availableFpsRange) {
- if (bestRange.getUpper() < r.getUpper()) {
- bestRange = r;
- } else if (bestRange.getUpper() == r.getUpper() &&
- bestRange.getLower() < r.getLower()) {
- bestRange = r;
- }
- }
- m.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, bestRange);
- }
-
- // control.sceneMode -- DISABLED is always available
- m.set(CaptureRequest.CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_DISABLED);
-
- // control.zoomRatio -- 1.0
- m.set(CaptureRequest.CONTROL_ZOOM_RATIO, 1.0f);
-
- /*
- * statistics.*
- */
-
- // statistics.faceDetectMode
- m.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, STATISTICS_FACE_DETECT_MODE_OFF);
-
- /*
- * flash.*
- */
-
- // flash.mode
- m.set(CaptureRequest.FLASH_MODE, FLASH_MODE_OFF);
-
- /*
- * noiseReduction.*
- */
- if (templateId == CameraDevice.TEMPLATE_STILL_CAPTURE) {
- m.set(CaptureRequest.NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_HIGH_QUALITY);
- } else {
- m.set(CaptureRequest.NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_FAST);
- }
-
- /*
- * colorCorrection.*
- */
- if (templateId == CameraDevice.TEMPLATE_STILL_CAPTURE) {
- m.set(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE,
- COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY);
- } else {
- m.set(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE,
- COLOR_CORRECTION_ABERRATION_MODE_FAST);
- }
-
- /*
- * lens.*
- */
-
- // lens.focalLength
- m.set(CaptureRequest.LENS_FOCAL_LENGTH,
- c.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)[0]);
-
- /*
- * jpeg.*
- */
-
- // jpeg.thumbnailSize - set smallest non-zero size if possible
- Size[] sizes = c.get(CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES);
- m.set(CaptureRequest.JPEG_THUMBNAIL_SIZE, (sizes.length > 1) ? sizes[1] : sizes[0]);
-
- // TODO: map other request template values
- return m;
- }
-
- private static int[] getTagsForKeys(Key<?>[] keys) {
- int[] tags = new int[keys.length];
-
- for (int i = 0; i < keys.length; ++i) {
- tags[i] = keys[i].getNativeKey().getTag();
- }
-
- return tags;
- }
-
- private static int[] getTagsForKeys(CaptureRequest.Key<?>[] keys) {
- int[] tags = new int[keys.length];
-
- for (int i = 0; i < keys.length; ++i) {
- tags[i] = keys[i].getNativeKey().getTag();
- }
-
- return tags;
- }
-
- private static int[] getTagsForKeys(CaptureResult.Key<?>[] keys) {
- int[] tags = new int[keys.length];
-
- for (int i = 0; i < keys.length; ++i) {
- tags[i] = keys[i].getNativeKey().getTag();
- }
-
- return tags;
- }
-
- /**
- * Convert the requested AF mode into its equivalent supported parameter.
- *
- * @param mode {@code CONTROL_AF_MODE}
- * @param supportedFocusModes list of camera1's supported focus modes
- * @return the stringified af mode, or {@code null} if its not supported
- */
- static String convertAfModeToLegacy(int mode, List<String> supportedFocusModes) {
- if (supportedFocusModes == null || supportedFocusModes.isEmpty()) {
- Log.w(TAG, "No focus modes supported; API1 bug");
- return null;
- }
-
- String param = null;
- switch (mode) {
- case CONTROL_AF_MODE_AUTO:
- param = Parameters.FOCUS_MODE_AUTO;
- break;
- case CONTROL_AF_MODE_CONTINUOUS_PICTURE:
- param = Parameters.FOCUS_MODE_CONTINUOUS_PICTURE;
- break;
- case CONTROL_AF_MODE_CONTINUOUS_VIDEO:
- param = Parameters.FOCUS_MODE_CONTINUOUS_VIDEO;
- break;
- case CONTROL_AF_MODE_EDOF:
- param = Parameters.FOCUS_MODE_EDOF;
- break;
- case CONTROL_AF_MODE_MACRO:
- param = Parameters.FOCUS_MODE_MACRO;
- break;
- case CONTROL_AF_MODE_OFF:
- if (supportedFocusModes.contains(Parameters.FOCUS_MODE_FIXED)) {
- param = Parameters.FOCUS_MODE_FIXED;
- } else {
- param = Parameters.FOCUS_MODE_INFINITY;
- }
- }
-
- if (!supportedFocusModes.contains(param)) {
- // Weed out bad user input by setting to the first arbitrary focus mode
- String defaultMode = supportedFocusModes.get(0);
- Log.w(TAG,
- String.format(
- "convertAfModeToLegacy - ignoring unsupported mode %d, " +
- "defaulting to %s", mode, defaultMode));
- param = defaultMode;
- }
-
- return param;
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyRequest.java b/core/java/android/hardware/camera2/legacy/LegacyRequest.java
deleted file mode 100644
index f13ac5c..0000000
--- a/core/java/android/hardware/camera2/legacy/LegacyRequest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.hardware.Camera;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CaptureRequest;
-import android.util.Size;
-
-import static com.android.internal.util.Preconditions.*;
-
-/**
- * Hold important data necessary to build the camera1 parameters up from a capture request.
- */
-public class LegacyRequest {
- /** Immutable characteristics for the camera corresponding to this request */
- public final CameraCharacteristics characteristics;
- /** Immutable capture request, as requested by the user */
- public final CaptureRequest captureRequest;
- /** Immutable api1 preview buffer size at the time of the request */
- public final Size previewSize;
- /** <em>Mutable</em> camera parameters */
- public final Camera.Parameters parameters;
-
- /**
- * Create a new legacy request; the parameters are copied.
- *
- * @param characteristics immutable static camera characteristics for this camera
- * @param captureRequest immutable user-defined capture request
- * @param previewSize immutable internal preview size used for {@link Camera#setPreviewSurface}
- * @param parameters the initial camera1 parameter state; (copied) can be mutated
- */
- public LegacyRequest(CameraCharacteristics characteristics, CaptureRequest captureRequest,
- Size previewSize, Camera.Parameters parameters) {
- this.characteristics = checkNotNull(characteristics, "characteristics must not be null");
- this.captureRequest = checkNotNull(captureRequest, "captureRequest must not be null");
- this.previewSize = checkNotNull(previewSize, "previewSize must not be null");
- checkNotNull(parameters, "parameters must not be null");
-
- this.parameters = Camera.getParametersCopy(parameters);
- }
-
- /**
- * Update the current parameters in-place to be a copy of the new parameters.
- *
- * @param parameters non-{@code null} parameters for api1 camera
- */
- public void setParameters(Camera.Parameters parameters) {
- checkNotNull(parameters, "parameters must not be null");
-
- this.parameters.copyFrom(parameters);
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
deleted file mode 100644
index 3a46379..0000000
--- a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
+++ /dev/null
@@ -1,688 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.graphics.Rect;
-import android.hardware.Camera;
-import android.hardware.Camera.Parameters;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.params.MeteringRectangle;
-import android.hardware.camera2.utils.ListUtils;
-import android.hardware.camera2.utils.ParamsUtils;
-import android.location.Location;
-import android.util.Log;
-import android.util.Range;
-import android.util.Size;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-
-import static android.hardware.camera2.CaptureRequest.*;
-
-/**
- * Provide legacy-specific implementations of camera2 CaptureRequest for legacy devices.
- */
-@SuppressWarnings("deprecation")
-public class LegacyRequestMapper {
- private static final String TAG = "LegacyRequestMapper";
- private static final boolean DEBUG = false;
-
- /** Default quality for android.jpeg.quality, android.jpeg.thumbnailQuality */
- private static final byte DEFAULT_JPEG_QUALITY = 85;
-
- /**
- * Set the legacy parameters using the {@link LegacyRequest legacy request}.
- *
- * <p>The legacy request's parameters are changed as a side effect of calling this
- * method.</p>
- *
- * @param legacyRequest a non-{@code null} legacy request
- */
- public static void convertRequestMetadata(LegacyRequest legacyRequest) {
- CameraCharacteristics characteristics = legacyRequest.characteristics;
- CaptureRequest request = legacyRequest.captureRequest;
- Size previewSize = legacyRequest.previewSize;
- Camera.Parameters params = legacyRequest.parameters;
-
- Rect activeArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
-
- /*
- * scaler.cropRegion
- */
- ParameterUtils.ZoomData zoomData;
- {
- zoomData = ParameterUtils.convertToLegacyZoom(activeArray,
- request.get(SCALER_CROP_REGION),
- request.get(CONTROL_ZOOM_RATIO),
- previewSize,
- params);
-
- if (params.isZoomSupported()) {
- params.setZoom(zoomData.zoomIndex);
- } else if (DEBUG) {
- Log.v(TAG, "convertRequestToMetadata - zoom is not supported");
- }
- }
-
- /*
- * colorCorrection.*
- */
- // colorCorrection.aberrationMode
- {
- int aberrationMode = ParamsUtils.getOrDefault(request,
- COLOR_CORRECTION_ABERRATION_MODE,
- /*defaultValue*/COLOR_CORRECTION_ABERRATION_MODE_FAST);
-
- if (aberrationMode != COLOR_CORRECTION_ABERRATION_MODE_FAST &&
- aberrationMode != COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY) {
- Log.w(TAG, "convertRequestToMetadata - Ignoring unsupported " +
- "colorCorrection.aberrationMode = " + aberrationMode);
- }
- }
-
- /*
- * control.ae*
- */
- // control.aeAntibandingMode
- {
- String legacyMode;
- Integer antiBandingMode = request.get(CONTROL_AE_ANTIBANDING_MODE);
- if (antiBandingMode != null) {
- legacyMode = convertAeAntiBandingModeToLegacy(antiBandingMode);
- } else {
- legacyMode = ListUtils.listSelectFirstFrom(params.getSupportedAntibanding(),
- new String[] {
- Parameters.ANTIBANDING_AUTO,
- Parameters.ANTIBANDING_OFF,
- Parameters.ANTIBANDING_50HZ,
- Parameters.ANTIBANDING_60HZ,
- });
- }
-
- if (legacyMode != null) {
- params.setAntibanding(legacyMode);
- }
- }
-
- /*
- * control.aeRegions, afRegions
- */
- {
- // aeRegions
- {
- // Use aeRegions if available, fall back to using awbRegions if present
- MeteringRectangle[] aeRegions = request.get(CONTROL_AE_REGIONS);
- if (request.get(CONTROL_AWB_REGIONS) != null) {
- Log.w(TAG, "convertRequestMetadata - control.awbRegions setting is not " +
- "supported, ignoring value");
- }
- int maxNumMeteringAreas = params.getMaxNumMeteringAreas();
- List<Camera.Area> meteringAreaList = convertMeteringRegionsToLegacy(
- activeArray, zoomData, aeRegions, maxNumMeteringAreas,
- /*regionName*/"AE");
-
- // WAR: for b/17252693, some devices can't handle params.setFocusAreas(null).
- if (maxNumMeteringAreas > 0) {
- params.setMeteringAreas(meteringAreaList);
- }
- }
-
- // afRegions
- {
- MeteringRectangle[] afRegions = request.get(CONTROL_AF_REGIONS);
- int maxNumFocusAreas = params.getMaxNumFocusAreas();
- List<Camera.Area> focusAreaList = convertMeteringRegionsToLegacy(
- activeArray, zoomData, afRegions, maxNumFocusAreas,
- /*regionName*/"AF");
-
- // WAR: for b/17252693, some devices can't handle params.setFocusAreas(null).
- if (maxNumFocusAreas > 0) {
- params.setFocusAreas(focusAreaList);
- }
- }
- }
-
- // control.aeTargetFpsRange
- Range<Integer> aeFpsRange = request.get(CONTROL_AE_TARGET_FPS_RANGE);
- if (aeFpsRange != null) {
- int[] legacyFps = convertAeFpsRangeToLegacy(aeFpsRange);
-
- int[] rangeToApply = null;
- for(int[] range : params.getSupportedPreviewFpsRange()) {
- // Round range up/down to integer FPS value
- int intRangeLow = (int) Math.floor(range[0] / 1000.0) * 1000;
- int intRangeHigh = (int) Math.ceil(range[1] / 1000.0) * 1000;
- if (legacyFps[0] == intRangeLow && legacyFps[1] == intRangeHigh) {
- rangeToApply = range;
- break;
- }
- }
- if (rangeToApply != null) {
- params.setPreviewFpsRange(rangeToApply[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
- rangeToApply[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
- } else {
- Log.w(TAG, "Unsupported FPS range set [" + legacyFps[0] + "," + legacyFps[1] + "]");
- }
- }
-
- /*
- * control
- */
-
- // control.aeExposureCompensation
- {
- Range<Integer> compensationRange =
- characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
- int compensation = ParamsUtils.getOrDefault(request,
- CONTROL_AE_EXPOSURE_COMPENSATION,
- /*defaultValue*/0);
-
- if (!compensationRange.contains(compensation)) {
- Log.w(TAG,
- "convertRequestMetadata - control.aeExposureCompensation " +
- "is out of range, ignoring value");
- compensation = 0;
- }
-
- params.setExposureCompensation(compensation);
- }
-
- // control.aeLock
- {
- Boolean aeLock = getIfSupported(request, CONTROL_AE_LOCK, /*defaultValue*/false,
- params.isAutoExposureLockSupported(),
- /*allowedValue*/false);
-
- if (aeLock != null) {
- params.setAutoExposureLock(aeLock);
- }
-
- if (DEBUG) {
- Log.v(TAG, "convertRequestToMetadata - control.aeLock set to " + aeLock);
- }
-
- // TODO: Don't add control.aeLock to availableRequestKeys if it's not supported
- }
-
- // control.aeMode, flash.mode
- mapAeAndFlashMode(request, /*out*/params);
-
- // control.afMode
- {
- int afMode = ParamsUtils.getOrDefault(request, CONTROL_AF_MODE,
- /*defaultValue*/CONTROL_AF_MODE_OFF);
- String focusMode = LegacyMetadataMapper.convertAfModeToLegacy(afMode,
- params.getSupportedFocusModes());
-
- if (focusMode != null) {
- params.setFocusMode(focusMode);
- }
-
- if (DEBUG) {
- Log.v(TAG, "convertRequestToMetadata - control.afMode "
- + afMode + " mapped to " + focusMode);
- }
- }
-
- // control.awbMode
- {
- Integer awbMode = getIfSupported(request, CONTROL_AWB_MODE,
- /*defaultValue*/CONTROL_AWB_MODE_AUTO,
- params.getSupportedWhiteBalance() != null,
- /*allowedValue*/CONTROL_AWB_MODE_AUTO);
-
- String whiteBalanceMode = null;
- if (awbMode != null) { // null iff AWB is not supported by camera1 api
- whiteBalanceMode = convertAwbModeToLegacy(awbMode);
- params.setWhiteBalance(whiteBalanceMode);
- }
-
- if (DEBUG) {
- Log.v(TAG, "convertRequestToMetadata - control.awbMode "
- + awbMode + " mapped to " + whiteBalanceMode);
- }
- }
-
- // control.awbLock
- {
- Boolean awbLock = getIfSupported(request, CONTROL_AWB_LOCK, /*defaultValue*/false,
- params.isAutoWhiteBalanceLockSupported(),
- /*allowedValue*/false);
-
- if (awbLock != null) {
- params.setAutoWhiteBalanceLock(awbLock);
- }
-
- // TODO: Don't add control.awbLock to availableRequestKeys if it's not supported
- }
-
- // control.captureIntent
- {
- int captureIntent = ParamsUtils.getOrDefault(request,
- CONTROL_CAPTURE_INTENT,
- /*defaultValue*/CONTROL_CAPTURE_INTENT_PREVIEW);
-
- captureIntent = filterSupportedCaptureIntent(captureIntent);
-
- params.setRecordingHint(
- captureIntent == CONTROL_CAPTURE_INTENT_VIDEO_RECORD ||
- captureIntent == CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT);
- }
-
- // control.videoStabilizationMode
- {
- Integer stabMode = getIfSupported(request, CONTROL_VIDEO_STABILIZATION_MODE,
- /*defaultValue*/CONTROL_VIDEO_STABILIZATION_MODE_OFF,
- params.isVideoStabilizationSupported(),
- /*allowedValue*/CONTROL_VIDEO_STABILIZATION_MODE_OFF);
-
- if (stabMode != null) {
- params.setVideoStabilization(stabMode == CONTROL_VIDEO_STABILIZATION_MODE_ON);
- }
- }
-
- // lens.focusDistance
- {
- boolean infinityFocusSupported =
- ListUtils.listContains(params.getSupportedFocusModes(),
- Parameters.FOCUS_MODE_INFINITY);
- Float focusDistance = getIfSupported(request, LENS_FOCUS_DISTANCE,
- /*defaultValue*/0f, infinityFocusSupported, /*allowedValue*/0f);
-
- if (focusDistance == null || focusDistance != 0f) {
- Log.w(TAG,
- "convertRequestToMetadata - Ignoring android.lens.focusDistance "
- + infinityFocusSupported + ", only 0.0f is supported");
- }
- }
-
- // control.sceneMode, control.mode
- {
- // TODO: Map FACE_PRIORITY scene mode to face detection.
-
- if (params.getSupportedSceneModes() != null) {
- int controlMode = ParamsUtils.getOrDefault(request, CONTROL_MODE,
- /*defaultValue*/CONTROL_MODE_AUTO);
- String modeToSet;
- switch (controlMode) {
- case CONTROL_MODE_USE_SCENE_MODE: {
- int sceneMode = ParamsUtils.getOrDefault(request, CONTROL_SCENE_MODE,
- /*defaultValue*/CONTROL_SCENE_MODE_DISABLED);
- String legacySceneMode = LegacyMetadataMapper.
- convertSceneModeToLegacy(sceneMode);
- if (legacySceneMode != null) {
- modeToSet = legacySceneMode;
- } else {
- modeToSet = Parameters.SCENE_MODE_AUTO;
- Log.w(TAG, "Skipping unknown requested scene mode: " + sceneMode);
- }
- break;
- }
- case CONTROL_MODE_AUTO: {
- modeToSet = Parameters.SCENE_MODE_AUTO;
- break;
- }
- default: {
- Log.w(TAG, "Control mode " + controlMode +
- " is unsupported, defaulting to AUTO");
- modeToSet = Parameters.SCENE_MODE_AUTO;
- }
- }
- params.setSceneMode(modeToSet);
- }
- }
-
- // control.effectMode
- {
- if (params.getSupportedColorEffects() != null) {
- int effectMode = ParamsUtils.getOrDefault(request, CONTROL_EFFECT_MODE,
- /*defaultValue*/CONTROL_EFFECT_MODE_OFF);
- String legacyEffectMode = LegacyMetadataMapper.convertEffectModeToLegacy(effectMode);
- if (legacyEffectMode != null) {
- params.setColorEffect(legacyEffectMode);
- } else {
- params.setColorEffect(Parameters.EFFECT_NONE);
- Log.w(TAG, "Skipping unknown requested effect mode: " + effectMode);
- }
- }
- }
-
- /*
- * sensor
- */
-
- // sensor.testPattern
- {
- int testPatternMode = ParamsUtils.getOrDefault(request, SENSOR_TEST_PATTERN_MODE,
- /*defaultValue*/SENSOR_TEST_PATTERN_MODE_OFF);
- if (testPatternMode != SENSOR_TEST_PATTERN_MODE_OFF) {
- Log.w(TAG, "convertRequestToMetadata - ignoring sensor.testPatternMode "
- + testPatternMode + "; only OFF is supported");
- }
- }
-
- /*
- * jpeg.*
- */
-
- // jpeg.gpsLocation
- {
- Location location = request.get(JPEG_GPS_LOCATION);
- if (location != null) {
- if (checkForCompleteGpsData(location)) {
- params.setGpsAltitude(location.getAltitude());
- params.setGpsLatitude(location.getLatitude());
- params.setGpsLongitude(location.getLongitude());
- params.setGpsProcessingMethod(location.getProvider().toUpperCase());
- params.setGpsTimestamp(location.getTime());
- } else {
- Log.w(TAG, "Incomplete GPS parameters provided in location " + location);
- }
- } else {
- params.removeGpsData();
- }
- }
-
- // jpeg.orientation
- {
- Integer orientation = request.get(CaptureRequest.JPEG_ORIENTATION);
- params.setRotation(ParamsUtils.getOrDefault(request, JPEG_ORIENTATION,
- (orientation == null) ? 0 : orientation));
- }
-
- // jpeg.quality
- {
- params.setJpegQuality(0xFF & ParamsUtils.getOrDefault(request, JPEG_QUALITY,
- DEFAULT_JPEG_QUALITY));
- }
-
- // jpeg.thumbnailQuality
- {
- params.setJpegThumbnailQuality(0xFF & ParamsUtils.getOrDefault(request,
- JPEG_THUMBNAIL_QUALITY, DEFAULT_JPEG_QUALITY));
- }
-
- // jpeg.thumbnailSize
- {
- List<Camera.Size> sizes = params.getSupportedJpegThumbnailSizes();
-
- if (sizes != null && sizes.size() > 0) {
- Size s = request.get(JPEG_THUMBNAIL_SIZE);
- boolean invalidSize = (s == null) ? false : !ParameterUtils.containsSize(sizes,
- s.getWidth(), s.getHeight());
- if (invalidSize) {
- Log.w(TAG, "Invalid JPEG thumbnail size set " + s + ", skipping thumbnail...");
- }
- if (s == null || invalidSize) {
- // (0,0) = "no thumbnail" in Camera API 1
- params.setJpegThumbnailSize(/*width*/0, /*height*/0);
- } else {
- params.setJpegThumbnailSize(s.getWidth(), s.getHeight());
- }
- }
- }
-
- /*
- * noiseReduction.*
- */
- // noiseReduction.mode
- {
- int mode = ParamsUtils.getOrDefault(request,
- NOISE_REDUCTION_MODE,
- /*defaultValue*/NOISE_REDUCTION_MODE_FAST);
-
- if (mode != NOISE_REDUCTION_MODE_FAST &&
- mode != NOISE_REDUCTION_MODE_HIGH_QUALITY) {
- Log.w(TAG, "convertRequestToMetadata - Ignoring unsupported " +
- "noiseReduction.mode = " + mode);
- }
- }
- }
-
- private static boolean checkForCompleteGpsData(Location location) {
- return location != null && location.getProvider() != null && location.getTime() != 0;
- }
-
- static int filterSupportedCaptureIntent(int captureIntent) {
- switch (captureIntent) {
- case CONTROL_CAPTURE_INTENT_CUSTOM:
- case CONTROL_CAPTURE_INTENT_PREVIEW:
- case CONTROL_CAPTURE_INTENT_STILL_CAPTURE:
- case CONTROL_CAPTURE_INTENT_VIDEO_RECORD:
- case CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT:
- break;
- case CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG:
- case CONTROL_CAPTURE_INTENT_MANUAL:
- captureIntent = CONTROL_CAPTURE_INTENT_PREVIEW;
- Log.w(TAG, "Unsupported control.captureIntent value " + captureIntent
- + "; default to PREVIEW");
- default:
- captureIntent = CONTROL_CAPTURE_INTENT_PREVIEW;
- Log.w(TAG, "Unknown control.captureIntent value " + captureIntent
- + "; default to PREVIEW");
- }
-
- return captureIntent;
- }
-
- private static List<Camera.Area> convertMeteringRegionsToLegacy(
- Rect activeArray, ParameterUtils.ZoomData zoomData,
- MeteringRectangle[] meteringRegions, int maxNumMeteringAreas, String regionName) {
- if (meteringRegions == null || maxNumMeteringAreas <= 0) {
- if (maxNumMeteringAreas > 0) {
- return Arrays.asList(ParameterUtils.CAMERA_AREA_DEFAULT);
- } else {
- return null;
- }
- }
-
- // Add all non-zero weight regions to the list
- List<MeteringRectangle> meteringRectangleList = new ArrayList<>();
- for (MeteringRectangle rect : meteringRegions) {
- if (rect.getMeteringWeight() != MeteringRectangle.METERING_WEIGHT_DONT_CARE) {
- meteringRectangleList.add(rect);
- }
- }
-
- if (meteringRectangleList.size() == 0) {
- Log.w(TAG, "Only received metering rectangles with weight 0.");
- return Arrays.asList(ParameterUtils.CAMERA_AREA_DEFAULT);
- }
-
- // Ignore any regions beyond our maximum supported count
- int countMeteringAreas =
- Math.min(maxNumMeteringAreas, meteringRectangleList.size());
- List<Camera.Area> meteringAreaList = new ArrayList<>(countMeteringAreas);
-
- for (int i = 0; i < countMeteringAreas; ++i) {
- MeteringRectangle rect = meteringRectangleList.get(i);
-
- ParameterUtils.MeteringData meteringData =
- ParameterUtils.convertMeteringRectangleToLegacy(activeArray, rect, zoomData);
- meteringAreaList.add(meteringData.meteringArea);
- }
-
- if (maxNumMeteringAreas < meteringRectangleList.size()) {
- Log.w(TAG,
- "convertMeteringRegionsToLegacy - Too many requested " + regionName +
- " regions, ignoring all beyond the first " + maxNumMeteringAreas);
- }
-
- if (DEBUG) {
- Log.v(TAG, "convertMeteringRegionsToLegacy - " + regionName + " areas = "
- + ParameterUtils.stringFromAreaList(meteringAreaList));
- }
-
- return meteringAreaList;
- }
-
- private static void mapAeAndFlashMode(CaptureRequest r, /*out*/Parameters p) {
- int flashMode = ParamsUtils.getOrDefault(r, FLASH_MODE, FLASH_MODE_OFF);
- int aeMode = ParamsUtils.getOrDefault(r, CONTROL_AE_MODE, CONTROL_AE_MODE_ON);
-
- List<String> supportedFlashModes = p.getSupportedFlashModes();
-
- String flashModeSetting = null;
-
- // Flash is OFF by default, on cameras that support flash
- if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_OFF)) {
- flashModeSetting = Parameters.FLASH_MODE_OFF;
- }
-
- /*
- * Map all of the control.aeMode* enums, but ignore AE_MODE_OFF since we never support it
- */
-
- // Ignore flash.mode controls unless aeMode == ON
- if (aeMode == CONTROL_AE_MODE_ON) {
- if (flashMode == FLASH_MODE_TORCH) {
- if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_TORCH)) {
- flashModeSetting = Parameters.FLASH_MODE_TORCH;
- } else {
- Log.w(TAG, "mapAeAndFlashMode - Ignore flash.mode == TORCH;" +
- "camera does not support it");
- }
- } else if (flashMode == FLASH_MODE_SINGLE) {
- if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_ON)) {
- flashModeSetting = Parameters.FLASH_MODE_ON;
- } else {
- Log.w(TAG, "mapAeAndFlashMode - Ignore flash.mode == SINGLE;" +
- "camera does not support it");
- }
- } else {
- // Use the default FLASH_MODE_OFF
- }
- } else if (aeMode == CONTROL_AE_MODE_ON_ALWAYS_FLASH) {
- if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_ON)) {
- flashModeSetting = Parameters.FLASH_MODE_ON;
- } else {
- Log.w(TAG, "mapAeAndFlashMode - Ignore control.aeMode == ON_ALWAYS_FLASH;" +
- "camera does not support it");
- }
- } else if (aeMode == CONTROL_AE_MODE_ON_AUTO_FLASH) {
- if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_AUTO)) {
- flashModeSetting = Parameters.FLASH_MODE_AUTO;
- } else {
- Log.w(TAG, "mapAeAndFlashMode - Ignore control.aeMode == ON_AUTO_FLASH;" +
- "camera does not support it");
- }
- } else if (aeMode == CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE) {
- if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_RED_EYE)) {
- flashModeSetting = Parameters.FLASH_MODE_RED_EYE;
- } else {
- Log.w(TAG, "mapAeAndFlashMode - Ignore control.aeMode == ON_AUTO_FLASH_REDEYE;"
- + "camera does not support it");
- }
- } else {
- // Default to aeMode == ON, flash = OFF
- }
-
- if (flashModeSetting != null) {
- p.setFlashMode(flashModeSetting);
- }
-
- if (DEBUG) {
- Log.v(TAG,
- "mapAeAndFlashMode - set flash.mode (api1) to " + flashModeSetting
- + ", requested (api2) " + flashMode
- + ", supported (api1) " + ListUtils.listToString(supportedFlashModes));
- }
- }
-
- /**
- * Returns null if the anti-banding mode enum is not supported.
- */
- private static String convertAeAntiBandingModeToLegacy(int mode) {
- switch (mode) {
- case CONTROL_AE_ANTIBANDING_MODE_OFF: {
- return Parameters.ANTIBANDING_OFF;
- }
- case CONTROL_AE_ANTIBANDING_MODE_50HZ: {
- return Parameters.ANTIBANDING_50HZ;
- }
- case CONTROL_AE_ANTIBANDING_MODE_60HZ: {
- return Parameters.ANTIBANDING_60HZ;
- }
- case CONTROL_AE_ANTIBANDING_MODE_AUTO: {
- return Parameters.ANTIBANDING_AUTO;
- }
- default: {
- return null;
- }
- }
- }
-
- private static int[] convertAeFpsRangeToLegacy(Range<Integer> fpsRange) {
- int[] legacyFps = new int[2];
- legacyFps[Parameters.PREVIEW_FPS_MIN_INDEX] = fpsRange.getLower() * 1000;
- legacyFps[Parameters.PREVIEW_FPS_MAX_INDEX] = fpsRange.getUpper() * 1000;
- return legacyFps;
- }
-
- private static String convertAwbModeToLegacy(int mode) {
- switch (mode) {
- case CONTROL_AWB_MODE_AUTO:
- return Camera.Parameters.WHITE_BALANCE_AUTO;
- case CONTROL_AWB_MODE_INCANDESCENT:
- return Camera.Parameters.WHITE_BALANCE_INCANDESCENT;
- case CONTROL_AWB_MODE_FLUORESCENT:
- return Camera.Parameters.WHITE_BALANCE_FLUORESCENT;
- case CONTROL_AWB_MODE_WARM_FLUORESCENT:
- return Camera.Parameters.WHITE_BALANCE_WARM_FLUORESCENT;
- case CONTROL_AWB_MODE_DAYLIGHT:
- return Camera.Parameters.WHITE_BALANCE_DAYLIGHT;
- case CONTROL_AWB_MODE_CLOUDY_DAYLIGHT:
- return Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT;
- case CONTROL_AWB_MODE_TWILIGHT:
- return Camera.Parameters.WHITE_BALANCE_TWILIGHT;
- case CONTROL_AWB_MODE_SHADE:
- return Parameters.WHITE_BALANCE_SHADE;
- default:
- Log.w(TAG, "convertAwbModeToLegacy - unrecognized control.awbMode" + mode);
- return Camera.Parameters.WHITE_BALANCE_AUTO;
- }
- }
-
-
- /**
- * Return {@code null} if the value is not supported, otherwise return the retrieved key's
- * value from the request (or the default value if it wasn't set).
- *
- * <p>If the fetched value in the request is equivalent to {@code allowedValue},
- * then omit the warning (e.g. turning off AF lock on a camera
- * that always has the AF lock turned off is a silent no-op), but still return {@code null}.</p>
- *
- * <p>Logs a warning to logcat if the key is not supported by api1 camera device.</p.
- */
- private static <T> T getIfSupported(
- CaptureRequest r, CaptureRequest.Key<T> key, T defaultValue, boolean isSupported,
- T allowedValue) {
- T val = ParamsUtils.getOrDefault(r, key, defaultValue);
-
- if (!isSupported) {
- if (!Objects.equals(val, allowedValue)) {
- Log.w(TAG, key.getName() + " is not supported; ignoring requested value " + val);
- }
- return null;
- }
-
- return val;
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
deleted file mode 100644
index 09edf74..0000000
--- a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
+++ /dev/null
@@ -1,529 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.graphics.Rect;
-import android.hardware.Camera;
-import android.hardware.Camera.Parameters;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.legacy.ParameterUtils.WeightedRectangle;
-import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
-import android.hardware.camera2.params.MeteringRectangle;
-import android.hardware.camera2.utils.ListUtils;
-import android.hardware.camera2.utils.ParamsUtils;
-import android.util.Log;
-import android.util.Size;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static android.hardware.camera2.CaptureResult.*;
-
-/**
- * Provide legacy-specific implementations of camera2 CaptureResult for legacy devices.
- */
-@SuppressWarnings("deprecation")
-public class LegacyResultMapper {
- private static final String TAG = "LegacyResultMapper";
- private static final boolean DEBUG = false;
-
- private LegacyRequest mCachedRequest = null;
- private CameraMetadataNative mCachedResult = null;
-
- /**
- * Generate capture result metadata from the legacy camera request.
- *
- * <p>This method caches and reuses the result from the previous call to this method if
- * the {@code parameters} of the subsequent {@link LegacyRequest} passed to this method
- * have not changed.</p>
- *
- * @param legacyRequest a non-{@code null} legacy request containing the latest parameters
- * @param timestamp the timestamp to use for this result in nanoseconds.
- *
- * @return {@link CameraMetadataNative} object containing result metadata.
- */
- public CameraMetadataNative cachedConvertResultMetadata(
- LegacyRequest legacyRequest, long timestamp) {
- CameraMetadataNative result;
- boolean cached;
-
- /*
- * Attempt to look up the result from the cache if the parameters haven't changed
- */
- if (mCachedRequest != null &&
- legacyRequest.parameters.same(mCachedRequest.parameters) &&
- legacyRequest.captureRequest.equals(mCachedRequest.captureRequest)) {
- result = new CameraMetadataNative(mCachedResult);
- cached = true;
- } else {
- result = convertResultMetadata(legacyRequest);
- cached = false;
-
- // Always cache a *copy* of the metadata result,
- // since api2's client side takes ownership of it after it receives a result
- mCachedRequest = legacyRequest;
- mCachedResult = new CameraMetadataNative(result);
- }
-
- /*
- * Unconditionally set fields that change in every single frame
- */
- {
- // sensor.timestamp
- result.set(SENSOR_TIMESTAMP, timestamp);
- }
-
- if (DEBUG) {
- Log.v(TAG, "cachedConvertResultMetadata - cached? " + cached +
- " timestamp = " + timestamp);
-
- Log.v(TAG, "----- beginning of result dump ------");
- result.dumpToLog();
- Log.v(TAG, "----- end of result dump ------");
- }
-
- return result;
- }
-
- /**
- * Generate capture result metadata from the legacy camera request.
- *
- * @param legacyRequest a non-{@code null} legacy request containing the latest parameters
- * @return a {@link CameraMetadataNative} object containing result metadata.
- */
- private static CameraMetadataNative convertResultMetadata(LegacyRequest legacyRequest) {
- CameraCharacteristics characteristics = legacyRequest.characteristics;
- CaptureRequest request = legacyRequest.captureRequest;
- Size previewSize = legacyRequest.previewSize;
- Camera.Parameters params = legacyRequest.parameters;
-
- CameraMetadataNative result = new CameraMetadataNative();
-
- Rect activeArraySize = characteristics.get(
- CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
- ZoomData zoomData = ParameterUtils.convertToLegacyZoom(activeArraySize,
- request.get(CaptureRequest.SCALER_CROP_REGION),
- request.get(CaptureRequest.CONTROL_ZOOM_RATIO),
- previewSize, params);
-
- /*
- * colorCorrection
- */
- // colorCorrection.aberrationMode
- {
- result.set(COLOR_CORRECTION_ABERRATION_MODE,
- request.get(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE));
- }
-
- /*
- * control
- */
-
- /*
- * control.ae*
- */
- mapAe(result, characteristics, request, activeArraySize, zoomData, /*out*/params);
-
- /*
- * control.af*
- */
- mapAf(result, activeArraySize, zoomData, /*out*/params);
-
- /*
- * control.awb*
- */
- mapAwb(result, /*out*/params);
-
- /*
- * control.captureIntent
- */
- {
- int captureIntent = ParamsUtils.getOrDefault(request,
- CaptureRequest.CONTROL_CAPTURE_INTENT,
- /*defaultValue*/CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW);
-
- captureIntent = LegacyRequestMapper.filterSupportedCaptureIntent(captureIntent);
-
- result.set(CONTROL_CAPTURE_INTENT, captureIntent);
- }
-
- /*
- * control.mode
- */
- {
- int controlMode = ParamsUtils.getOrDefault(request, CaptureRequest.CONTROL_MODE,
- CONTROL_MODE_AUTO);
- if (controlMode == CaptureResult.CONTROL_MODE_USE_SCENE_MODE) {
- result.set(CONTROL_MODE, CONTROL_MODE_USE_SCENE_MODE);
- } else {
- result.set(CONTROL_MODE, CONTROL_MODE_AUTO);
- }
- }
-
- /*
- * control.sceneMode
- */
- {
- String legacySceneMode = params.getSceneMode();
- int mode = LegacyMetadataMapper.convertSceneModeFromLegacy(legacySceneMode);
- if (mode != LegacyMetadataMapper.UNKNOWN_MODE) {
- result.set(CaptureResult.CONTROL_SCENE_MODE, mode);
- // In case of SCENE_MODE == FACE_PRIORITY, LegacyFaceDetectMapper will override
- // the result to say SCENE_MODE == FACE_PRIORITY.
- } else {
- Log.w(TAG, "Unknown scene mode " + legacySceneMode +
- " returned by camera HAL, setting to disabled.");
- result.set(CaptureResult.CONTROL_SCENE_MODE, CONTROL_SCENE_MODE_DISABLED);
- }
- }
-
- /*
- * control.effectMode
- */
- {
- String legacyEffectMode = params.getColorEffect();
- int mode = LegacyMetadataMapper.convertEffectModeFromLegacy(legacyEffectMode);
- if (mode != LegacyMetadataMapper.UNKNOWN_MODE) {
- result.set(CaptureResult.CONTROL_EFFECT_MODE, mode);
- } else {
- Log.w(TAG, "Unknown effect mode " + legacyEffectMode +
- " returned by camera HAL, setting to off.");
- result.set(CaptureResult.CONTROL_EFFECT_MODE, CONTROL_EFFECT_MODE_OFF);
- }
- }
-
- // control.videoStabilizationMode
- {
- int stabMode =
- (params.isVideoStabilizationSupported() && params.getVideoStabilization()) ?
- CONTROL_VIDEO_STABILIZATION_MODE_ON :
- CONTROL_VIDEO_STABILIZATION_MODE_OFF;
- result.set(CONTROL_VIDEO_STABILIZATION_MODE, stabMode);
- }
-
- /*
- * flash
- */
- {
- // flash.mode, flash.state mapped in mapAeAndFlashMode
- }
-
- /*
- * lens
- */
- // lens.focusDistance
- {
- if (Parameters.FOCUS_MODE_INFINITY.equals(params.getFocusMode())) {
- result.set(CaptureResult.LENS_FOCUS_DISTANCE, 0.0f);
- }
- }
-
- // lens.focalLength
- result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength());
-
- /*
- * request
- */
- // request.pipelineDepth
- result.set(REQUEST_PIPELINE_DEPTH,
- characteristics.get(CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH));
-
- /*
- * scaler
- */
- mapScaler(result, zoomData, /*out*/params);
-
- /*
- * sensor
- */
- // sensor.timestamp varies every frame; mapping is done in #cachedConvertResultMetadata
- {
- // Unconditionally no test patterns
- result.set(SENSOR_TEST_PATTERN_MODE, SENSOR_TEST_PATTERN_MODE_OFF);
- }
-
- /*
- * jpeg
- */
- // jpeg.gpsLocation
- result.set(JPEG_GPS_LOCATION, request.get(CaptureRequest.JPEG_GPS_LOCATION));
-
- // jpeg.orientation
- result.set(JPEG_ORIENTATION, request.get(CaptureRequest.JPEG_ORIENTATION));
-
- // jpeg.quality
- result.set(JPEG_QUALITY, (byte) params.getJpegQuality());
-
- // jpeg.thumbnailQuality
- result.set(JPEG_THUMBNAIL_QUALITY, (byte) params.getJpegThumbnailQuality());
-
- // jpeg.thumbnailSize
- Camera.Size s = params.getJpegThumbnailSize();
- if (s != null) {
- result.set(JPEG_THUMBNAIL_SIZE, ParameterUtils.convertSize(s));
- } else {
- Log.w(TAG, "Null thumbnail size received from parameters.");
- }
-
- /*
- * noiseReduction.*
- */
- // noiseReduction.mode
- result.set(NOISE_REDUCTION_MODE, request.get(CaptureRequest.NOISE_REDUCTION_MODE));
-
- return result;
- }
-
- private static void mapAe(CameraMetadataNative m,
- CameraCharacteristics characteristics,
- CaptureRequest request, Rect activeArray, ZoomData zoomData, /*out*/Parameters p) {
- // control.aeAntiBandingMode
- {
- int antiBandingMode = LegacyMetadataMapper.convertAntiBandingModeOrDefault(
- p.getAntibanding());
- m.set(CONTROL_AE_ANTIBANDING_MODE, antiBandingMode);
- }
-
- // control.aeExposureCompensation
- {
- m.set(CONTROL_AE_EXPOSURE_COMPENSATION, p.getExposureCompensation());
- }
-
- // control.aeLock
- {
- boolean lock = p.isAutoExposureLockSupported() ? p.getAutoExposureLock() : false;
- m.set(CONTROL_AE_LOCK, lock);
- if (DEBUG) {
- Log.v(TAG,
- "mapAe - android.control.aeLock = " + lock +
- ", supported = " + p.isAutoExposureLockSupported());
- }
-
- Boolean requestLock = request.get(CaptureRequest.CONTROL_AE_LOCK);
- if (requestLock != null && requestLock != lock) {
- Log.w(TAG,
- "mapAe - android.control.aeLock was requested to " + requestLock +
- " but resulted in " + lock);
- }
- }
-
- // control.aeMode, flash.mode, flash.state
- mapAeAndFlashMode(m, characteristics, p);
-
- // control.aeState
- if (LegacyMetadataMapper.LIE_ABOUT_AE_STATE) {
- // Lie to pass CTS temporarily.
- // TODO: Implement precapture trigger, after which we can report CONVERGED ourselves
- m.set(CONTROL_AE_STATE, CONTROL_AE_STATE_CONVERGED);
- }
-
- // control.aeRegions
- if (p.getMaxNumMeteringAreas() > 0) {
- if (DEBUG) {
- String meteringAreas = p.get("metering-areas");
- Log.v(TAG, "mapAe - parameter dump; metering-areas: " + meteringAreas);
- }
-
- MeteringRectangle[] meteringRectArray = getMeteringRectangles(activeArray,
- zoomData, p.getMeteringAreas(), "AE");
-
- m.set(CONTROL_AE_REGIONS, meteringRectArray);
- }
-
- }
-
- private static void mapAf(CameraMetadataNative m,
- Rect activeArray, ZoomData zoomData, Camera.Parameters p) {
- // control.afMode
- m.set(CaptureResult.CONTROL_AF_MODE, convertLegacyAfMode(p.getFocusMode()));
-
- // control.afRegions
- if (p.getMaxNumFocusAreas() > 0) {
- if (DEBUG) {
- String focusAreas = p.get("focus-areas");
- Log.v(TAG, "mapAe - parameter dump; focus-areas: " + focusAreas);
- }
-
- MeteringRectangle[] meteringRectArray = getMeteringRectangles(activeArray,
- zoomData, p.getFocusAreas(), "AF");
-
- m.set(CONTROL_AF_REGIONS, meteringRectArray);
- }
- }
-
- private static void mapAwb(CameraMetadataNative m, Camera.Parameters p) {
- // control.awbLock
- {
- boolean lock = p.isAutoWhiteBalanceLockSupported() ?
- p.getAutoWhiteBalanceLock() : false;
- m.set(CONTROL_AWB_LOCK, lock);
- }
-
- // control.awbMode
- {
- int awbMode = convertLegacyAwbMode(p.getWhiteBalance());
- m.set(CONTROL_AWB_MODE, awbMode);
- }
- }
-
- private static MeteringRectangle[] getMeteringRectangles(Rect activeArray, ZoomData zoomData,
- List<Camera.Area> meteringAreaList, String regionName) {
- List<MeteringRectangle> meteringRectList = new ArrayList<>();
- if (meteringAreaList != null) {
- for (Camera.Area area : meteringAreaList) {
- WeightedRectangle rect =
- ParameterUtils.convertCameraAreaToActiveArrayRectangle(
- activeArray, zoomData, area);
-
- meteringRectList.add(rect.toMetering());
- }
- }
-
- if (DEBUG) {
- Log.v(TAG,
- "Metering rectangles for " + regionName + ": "
- + ListUtils.listToString(meteringRectList));
- }
-
- return meteringRectList.toArray(new MeteringRectangle[0]);
- }
-
- /** Map results for control.aeMode, flash.mode, flash.state */
- private static void mapAeAndFlashMode(CameraMetadataNative m,
- CameraCharacteristics characteristics, Parameters p) {
- // Default: AE mode on but flash never fires
- int flashMode = FLASH_MODE_OFF;
- // If there is no flash on this camera, the state is always unavailable
- // , otherwise it's only known for TORCH/SINGLE modes
- Integer flashState = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)
- ? null : FLASH_STATE_UNAVAILABLE;
- int aeMode = CONTROL_AE_MODE_ON;
-
- String flashModeSetting = p.getFlashMode();
-
- if (flashModeSetting != null) {
- switch (flashModeSetting) {
- case Parameters.FLASH_MODE_OFF:
- break; // ok, using default
- case Parameters.FLASH_MODE_AUTO:
- aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH;
- break;
- case Parameters.FLASH_MODE_ON:
- // flashMode = SINGLE + aeMode = ON is indistinguishable from ON_ALWAYS_FLASH
- flashMode = FLASH_MODE_SINGLE;
- aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH;
- flashState = FLASH_STATE_FIRED;
- break;
- case Parameters.FLASH_MODE_RED_EYE:
- aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
- break;
- case Parameters.FLASH_MODE_TORCH:
- flashMode = FLASH_MODE_TORCH;
- flashState = FLASH_STATE_FIRED;
- break;
- default:
- Log.w(TAG,
- "mapAeAndFlashMode - Ignoring unknown flash mode " + p.getFlashMode());
- }
- }
-
- // flash.state
- m.set(FLASH_STATE, flashState);
- // flash.mode
- m.set(FLASH_MODE, flashMode);
- // control.aeMode
- m.set(CONTROL_AE_MODE, aeMode);
- }
-
- private static int convertLegacyAfMode(String mode) {
- if (mode == null) {
- Log.w(TAG, "convertLegacyAfMode - no AF mode, default to OFF");
- return CONTROL_AF_MODE_OFF;
- }
-
- switch (mode) {
- case Parameters.FOCUS_MODE_AUTO:
- return CONTROL_AF_MODE_AUTO;
- case Parameters.FOCUS_MODE_CONTINUOUS_PICTURE:
- return CONTROL_AF_MODE_CONTINUOUS_PICTURE;
- case Parameters.FOCUS_MODE_CONTINUOUS_VIDEO:
- return CONTROL_AF_MODE_CONTINUOUS_VIDEO;
- case Parameters.FOCUS_MODE_EDOF:
- return CONTROL_AF_MODE_EDOF;
- case Parameters.FOCUS_MODE_MACRO:
- return CONTROL_AF_MODE_MACRO;
- case Parameters.FOCUS_MODE_FIXED:
- return CONTROL_AF_MODE_OFF;
- case Parameters.FOCUS_MODE_INFINITY:
- return CONTROL_AF_MODE_OFF;
- default:
- Log.w(TAG, "convertLegacyAfMode - unknown mode " + mode + " , ignoring");
- return CONTROL_AF_MODE_OFF;
- }
- }
-
- private static int convertLegacyAwbMode(String mode) {
- if (mode == null) {
- // OK: camera1 api may not support changing WB modes; assume AUTO
- return CONTROL_AWB_MODE_AUTO;
- }
-
- switch (mode) {
- case Camera.Parameters.WHITE_BALANCE_AUTO:
- return CONTROL_AWB_MODE_AUTO;
- case Camera.Parameters.WHITE_BALANCE_INCANDESCENT:
- return CONTROL_AWB_MODE_INCANDESCENT;
- case Camera.Parameters.WHITE_BALANCE_FLUORESCENT:
- return CONTROL_AWB_MODE_FLUORESCENT;
- case Camera.Parameters.WHITE_BALANCE_WARM_FLUORESCENT:
- return CONTROL_AWB_MODE_WARM_FLUORESCENT;
- case Camera.Parameters.WHITE_BALANCE_DAYLIGHT:
- return CONTROL_AWB_MODE_DAYLIGHT;
- case Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT:
- return CONTROL_AWB_MODE_CLOUDY_DAYLIGHT;
- case Camera.Parameters.WHITE_BALANCE_TWILIGHT:
- return CONTROL_AWB_MODE_TWILIGHT;
- case Camera.Parameters.WHITE_BALANCE_SHADE:
- return CONTROL_AWB_MODE_SHADE;
- default:
- Log.w(TAG, "convertAwbMode - unrecognized WB mode " + mode);
- return CONTROL_AWB_MODE_AUTO;
- }
- }
-
- /** Map results for scaler.* */
- private static void mapScaler(CameraMetadataNative m,
- ZoomData zoomData,
- /*out*/Parameters p) {
- /*
- * scaler.cropRegion
- */
- {
- m.set(SCALER_CROP_REGION, zoomData.reportedCrop);
- }
-
- /*
- * control.zoomRatio
- */
- {
- m.set(CONTROL_ZOOM_RATIO, zoomData.reportedZoomRatio);
- }
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/ParameterUtils.java b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
deleted file mode 100644
index eb43598..0000000
--- a/core/java/android/hardware/camera2/legacy/ParameterUtils.java
+++ /dev/null
@@ -1,1099 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.hardware.Camera;
-import android.hardware.Camera.Area;
-import android.hardware.camera2.params.Face;
-import android.hardware.camera2.params.MeteringRectangle;
-import android.hardware.camera2.utils.ListUtils;
-import android.hardware.camera2.utils.ParamsUtils;
-import android.hardware.camera2.utils.SizeAreaComparator;
-import android.util.Size;
-import android.util.SizeF;
-
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import static com.android.internal.util.Preconditions.*;
-
-/**
- * Various utilities for dealing with camera API1 parameters.
- */
-@SuppressWarnings("deprecation")
-public class ParameterUtils {
- /** Upper/left minimal point of a normalized rectangle */
- public static final int NORMALIZED_RECTANGLE_MIN = -1000;
- /** Lower/right maximal point of a normalized rectangle */
- public static final int NORMALIZED_RECTANGLE_MAX = 1000;
- /** The default normalized rectangle spans the entire size of the preview viewport */
- public static final Rect NORMALIZED_RECTANGLE_DEFAULT = new Rect(
- NORMALIZED_RECTANGLE_MIN,
- NORMALIZED_RECTANGLE_MIN,
- NORMALIZED_RECTANGLE_MAX,
- NORMALIZED_RECTANGLE_MAX);
- /** The default normalized area uses the default normalized rectangle with a weight=1 */
- public static final Camera.Area CAMERA_AREA_DEFAULT =
- new Camera.Area(new Rect(NORMALIZED_RECTANGLE_DEFAULT),
- /*weight*/1);
- /** Empty rectangle {@code 0x0+0,0} */
- public static final Rect RECTANGLE_EMPTY =
- new Rect(/*left*/0, /*top*/0, /*right*/0, /*bottom*/0);
-
- private static final double ASPECT_RATIO_TOLERANCE = 0.05f;
-
- /**
- * Calculate effective/reported zoom data from a user-specified crop region.
- */
- public static class ZoomData {
- /** Zoom index used by {@link Camera.Parameters#setZoom} */
- public final int zoomIndex;
- /** Effective crop-region given the zoom index, coordinates relative to active-array */
- public final Rect previewCrop;
- /** Reported crop-region given the zoom index, coordinates relative to active-array */
- public final Rect reportedCrop;
- /** Reported zoom ratio given the zoom index */
- public final float reportedZoomRatio;
-
- public ZoomData(int zoomIndex, Rect previewCrop, Rect reportedCrop,
- float reportedZoomRatio) {
- this.zoomIndex = zoomIndex;
- this.previewCrop = previewCrop;
- this.reportedCrop = reportedCrop;
- this.reportedZoomRatio = reportedZoomRatio;
- }
- }
-
- /**
- * Calculate effective/reported metering data from a user-specified metering region.
- */
- public static class MeteringData {
- /**
- * The metering area scaled to the range of [-1000, 1000].
- * <p>Values outside of this range are clipped to be within the range.</p>
- */
- public final Camera.Area meteringArea;
- /**
- * Effective preview metering region, coordinates relative to active-array.
- *
- * <p>Clipped to fit inside of the (effective) preview crop region.</p>
- */
- public final Rect previewMetering;
- /**
- * Reported metering region, coordinates relative to active-array.
- *
- * <p>Clipped to fit inside of the (reported) resulting crop region.</p>
- */
- public final Rect reportedMetering;
-
- public MeteringData(Area meteringArea, Rect previewMetering, Rect reportedMetering) {
- this.meteringArea = meteringArea;
- this.previewMetering = previewMetering;
- this.reportedMetering = reportedMetering;
- }
- }
-
- /**
- * A weighted rectangle is an arbitrary rectangle (the coordinate system is unknown) with an
- * arbitrary weight.
- *
- * <p>The user of this class must know what the coordinate system ahead of time; it's
- * then possible to convert to a more concrete type such as a metering rectangle or a face.
- * </p>
- *
- * <p>When converting to a more concrete type, out-of-range values are clipped; this prevents
- * possible illegal argument exceptions being thrown at runtime.</p>
- */
- public static class WeightedRectangle {
- /** Arbitrary rectangle (the range is user-defined); never {@code null}. */
- public final Rect rect;
- /** Arbitrary weight (the range is user-defined). */
- public final int weight;
-
- /**
- * Create a new weighted-rectangle from a non-{@code null} rectangle; the {@code weight}
- * can be unbounded.
- */
- public WeightedRectangle(Rect rect, int weight) {
- this.rect = checkNotNull(rect, "rect must not be null");
- this.weight = weight;
- }
-
- /**
- * Convert to a metering rectangle, clipping any of the values to stay within range.
- *
- * <p>If values are clipped, a warning is printed to logcat.</p>
- *
- * @return a new metering rectangle
- */
- public MeteringRectangle toMetering() {
- int weight = clip(this.weight,
- MeteringRectangle.METERING_WEIGHT_MIN,
- MeteringRectangle.METERING_WEIGHT_MAX,
- rect,
- "weight");
-
- int x = clipLower(rect.left, /*lo*/0, rect, "left");
- int y = clipLower(rect.top, /*lo*/0, rect, "top");
- int w = clipLower(rect.width(), /*lo*/0, rect, "width");
- int h = clipLower(rect.height(), /*lo*/0, rect, "height");
-
- return new MeteringRectangle(x, y, w, h, weight);
- }
-
- /**
- * Convert to a face; the rect is considered to be the bounds, and the weight
- * is considered to be the score.
- *
- * <p>If the score is out of range of {@value Face#SCORE_MIN}, {@value Face#SCORE_MAX},
- * the score is clipped first and a warning is printed to logcat.</p>
- *
- * <p>If the id is negative, the id is changed to 0 and a warning is printed to
- * logcat.</p>
- *
- * <p>All other parameters are passed-through as-is.</p>
- *
- * @return a new face with the optional features set
- */
- public Face toFace(
- int id, Point leftEyePosition, Point rightEyePosition, Point mouthPosition) {
- int idSafe = clipLower(id, /*lo*/0, rect, "id");
- int score = clip(weight,
- Face.SCORE_MIN,
- Face.SCORE_MAX,
- rect,
- "score");
-
- return new Face(rect, score, idSafe, leftEyePosition, rightEyePosition, mouthPosition);
- }
-
- /**
- * Convert to a face; the rect is considered to be the bounds, and the weight
- * is considered to be the score.
- *
- * <p>If the score is out of range of {@value Face#SCORE_MIN}, {@value Face#SCORE_MAX},
- * the score is clipped first and a warning is printed to logcat.</p>
- *
- * <p>All other parameters are passed-through as-is.</p>
- *
- * @return a new face without the optional features
- */
- public Face toFace() {
- int score = clip(weight,
- Face.SCORE_MIN,
- Face.SCORE_MAX,
- rect,
- "score");
-
- return new Face(rect, score);
- }
-
- private static int clipLower(int value, int lo, Rect rect, String name) {
- return clip(value, lo, /*hi*/Integer.MAX_VALUE, rect, name);
- }
-
- private static int clip(int value, int lo, int hi, Rect rect, String name) {
- if (value < lo) {
- Log.w(TAG, "toMetering - Rectangle " + rect + " "
- + name + " too small, clip to " + lo);
- value = lo;
- } else if (value > hi) {
- Log.w(TAG, "toMetering - Rectangle " + rect + " "
- + name + " too small, clip to " + hi);
- value = hi;
- }
-
- return value;
- }
- }
-
- private static final String TAG = "ParameterUtils";
- private static final boolean DEBUG = false;
-
- /** getZoomRatios stores zoom ratios in 1/100 increments, e.x. a zoom of 3.2 is 320 */
- private static final int ZOOM_RATIO_MULTIPLIER = 100;
-
- /**
- * Convert a camera API1 size into a util size
- */
- public static Size convertSize(Camera.Size size) {
- checkNotNull(size, "size must not be null");
-
- return new Size(size.width, size.height);
- }
-
- /**
- * Convert a camera API1 list of sizes into a util list of sizes
- */
- public static List<Size> convertSizeList(List<Camera.Size> sizeList) {
- checkNotNull(sizeList, "sizeList must not be null");
-
- List<Size> sizes = new ArrayList<>(sizeList.size());
- for (Camera.Size s : sizeList) {
- sizes.add(new Size(s.width, s.height));
- }
- return sizes;
- }
-
- /**
- * Convert a camera API1 list of sizes into an array of sizes
- */
- public static Size[] convertSizeListToArray(List<Camera.Size> sizeList) {
- checkNotNull(sizeList, "sizeList must not be null");
-
- Size[] array = new Size[sizeList.size()];
- int ctr = 0;
- for (Camera.Size s : sizeList) {
- array[ctr++] = new Size(s.width, s.height);
- }
- return array;
- }
-
- /**
- * Check if the camera API1 list of sizes contains a size with the given dimens.
- */
- public static boolean containsSize(List<Camera.Size> sizeList, int width, int height) {
- checkNotNull(sizeList, "sizeList must not be null");
- for (Camera.Size s : sizeList) {
- if (s.height == height && s.width == width) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns the largest supported picture size, as compared by its area.
- */
- public static Size getLargestSupportedJpegSizeByArea(Camera.Parameters params) {
- checkNotNull(params, "params must not be null");
-
- List<Size> supportedJpegSizes = convertSizeList(params.getSupportedPictureSizes());
- return SizeAreaComparator.findLargestByArea(supportedJpegSizes);
- }
-
- /**
- * Convert a camera area into a human-readable string.
- */
- public static String stringFromArea(Camera.Area area) {
- if (area == null) {
- return null;
- } else {
- StringBuilder sb = new StringBuilder();
- Rect r = area.rect;
-
- sb.setLength(0);
- sb.append("(["); sb.append(r.left); sb.append(',');
- sb.append(r.top); sb.append("]["); sb.append(r.right);
- sb.append(','); sb.append(r.bottom); sb.append(']');
-
- sb.append(',');
- sb.append(area.weight);
- sb.append(')');
-
- return sb.toString();
- }
- }
-
- /**
- * Convert a camera area list into a human-readable string
- * @param areaList a list of areas (null is ok)
- */
- public static String stringFromAreaList(List<Camera.Area> areaList) {
- StringBuilder sb = new StringBuilder();
-
- if (areaList == null) {
- return null;
- }
-
- int i = 0;
- for (Camera.Area area : areaList) {
- if (area == null) {
- sb.append("null");
- } else {
- sb.append(stringFromArea(area));
- }
-
- if (i != areaList.size() - 1) {
- sb.append(", ");
- }
-
- i++;
- }
-
- return sb.toString();
- }
-
- /**
- * Calculate the closest zoom index for the user-requested crop region by rounding
- * up to the closest (largest or equal) possible zoom crop.
- *
- * <p>If the requested crop region exceeds the size of the active array, it is
- * shrunk to fit inside of the active array first.</p>
- *
- * <p>Since all api1 camera devices only support a discrete set of zooms, we have
- * to translate the per-pixel-granularity requested crop region into a per-zoom-index
- * granularity.</p>
- *
- * <p>Furthermore, since the zoom index and zoom levels also depends on the field-of-view
- * of the preview, the current preview {@code streamSize} is also used.</p>
- *
- * <p>The calculated crop regions are then written to in-place to {@code reportedCropRegion}
- * and {@code previewCropRegion}, in coordinates relative to the active array.</p>
- *
- * @param params non-{@code null} camera api1 parameters
- * @param activeArray active array dimensions, in sensor space
- * @param streamSize stream size dimensions, in pixels
- * @param cropRegion user-specified crop region, in active array coordinates
- * @param reportedCropRegion (out parameter) what the result for {@code cropRegion} looks like
- * @param previewCropRegion (out parameter) what the visual preview crop is
- * @return
- * the zoom index inclusively between 0 and {@code Parameters#getMaxZoom},
- * where 0 means the camera is not zoomed
- *
- * @throws NullPointerException if any of the args were {@code null}
- */
- public static int getClosestAvailableZoomCrop(
- Camera.Parameters params, Rect activeArray,
- Size streamSize, Rect cropRegion,
- /*out*/
- Rect reportedCropRegion,
- Rect previewCropRegion) {
- checkNotNull(params, "params must not be null");
- checkNotNull(activeArray, "activeArray must not be null");
- checkNotNull(streamSize, "streamSize must not be null");
- checkNotNull(reportedCropRegion, "reportedCropRegion must not be null");
- checkNotNull(previewCropRegion, "previewCropRegion must not be null");
-
- Rect actualCrop = new Rect(cropRegion);
-
- /*
- * Shrink requested crop region to fit inside of the active array size
- */
- if (!actualCrop.intersect(activeArray)) {
- Log.w(TAG, "getClosestAvailableZoomCrop - Crop region out of range; " +
- "setting to active array size");
- actualCrop.set(activeArray);
- }
-
- Rect previewCrop = getPreviewCropRectangleUnzoomed(activeArray, streamSize);
-
- // Make the user-requested crop region the same aspect ratio as the preview stream size
- Rect cropRegionAsPreview =
- shrinkToSameAspectRatioCentered(previewCrop, actualCrop);
-
- if (DEBUG) {
- Log.v(TAG, "getClosestAvailableZoomCrop - actualCrop = " + actualCrop);
- Log.v(TAG,
- "getClosestAvailableZoomCrop - previewCrop = " + previewCrop);
- Log.v(TAG,
- "getClosestAvailableZoomCrop - cropRegionAsPreview = " + cropRegionAsPreview);
- }
-
- /*
- * Iterate all available zoom rectangles and find the closest zoom index
- */
- Rect bestReportedCropRegion = null;
- Rect bestPreviewCropRegion = null;
- int bestZoomIndex = -1;
-
- List<Rect> availableReportedCropRegions =
- getAvailableZoomCropRectangles(params, activeArray);
- List<Rect> availablePreviewCropRegions =
- getAvailablePreviewZoomCropRectangles(params, activeArray, streamSize);
-
- if (DEBUG) {
- Log.v(TAG,
- "getClosestAvailableZoomCrop - availableReportedCropRegions = " +
- ListUtils.listToString(availableReportedCropRegions));
- Log.v(TAG,
- "getClosestAvailableZoomCrop - availablePreviewCropRegions = " +
- ListUtils.listToString(availablePreviewCropRegions));
- }
-
- if (availableReportedCropRegions.size() != availablePreviewCropRegions.size()) {
- throw new AssertionError("available reported/preview crop region size mismatch");
- }
-
- for (int i = 0; i < availableReportedCropRegions.size(); ++i) {
- Rect currentPreviewCropRegion = availablePreviewCropRegions.get(i);
- Rect currentReportedCropRegion = availableReportedCropRegions.get(i);
-
- boolean isBest;
- if (bestZoomIndex == -1) {
- isBest = true;
- } else if (currentPreviewCropRegion.width() >= cropRegionAsPreview.width() &&
- currentPreviewCropRegion.height() >= cropRegionAsPreview.height()) {
- isBest = true;
- } else {
- isBest = false;
- }
-
- // Sizes are sorted largest-to-smallest, so once the available crop is too small,
- // we the rest are too small. Furthermore, this is the final best crop,
- // since its the largest crop that still fits the requested crop
- if (isBest) {
- bestPreviewCropRegion = currentPreviewCropRegion;
- bestReportedCropRegion = currentReportedCropRegion;
- bestZoomIndex = i;
- } else {
- break;
- }
- }
-
- if (bestZoomIndex == -1) {
- // Even in the worst case, we should always at least return 0 here
- throw new AssertionError("Should've found at least one valid zoom index");
- }
-
- // Write the rectangles in-place
- reportedCropRegion.set(bestReportedCropRegion);
- previewCropRegion.set(bestPreviewCropRegion);
-
- return bestZoomIndex;
- }
-
- /**
- * Calculate the effective crop rectangle for this preview viewport;
- * assumes the preview is centered to the sensor and scaled to fit across one of the dimensions
- * without skewing.
- *
- * <p>The preview size must be a subset of the active array size; the resulting
- * rectangle will also be a subset of the active array rectangle.</p>
- *
- * <p>The unzoomed crop rectangle is calculated only.</p>
- *
- * @param activeArray active array dimensions, in sensor space
- * @param previewSize size of the preview buffer render target, in pixels (not in sensor space)
- * @return a rectangle which serves as the preview stream's effective crop region (unzoomed),
- * in sensor space
- *
- * @throws NullPointerException
- * if any of the args were {@code null}
- * @throws IllegalArgumentException
- * if {@code previewSize} is wider or taller than {@code activeArray}
- */
- private static Rect getPreviewCropRectangleUnzoomed(Rect activeArray, Size previewSize) {
- if (previewSize.getWidth() > activeArray.width()) {
- throw new IllegalArgumentException("previewSize must not be wider than activeArray");
- } else if (previewSize.getHeight() > activeArray.height()) {
- throw new IllegalArgumentException("previewSize must not be taller than activeArray");
- }
-
- float aspectRatioArray = activeArray.width() * 1.0f / activeArray.height();
- float aspectRatioPreview = previewSize.getWidth() * 1.0f / previewSize.getHeight();
-
- float cropH, cropW;
- if (Math.abs(aspectRatioPreview - aspectRatioArray) < ASPECT_RATIO_TOLERANCE) {
- cropH = activeArray.height();
- cropW = activeArray.width();
- } else if (aspectRatioPreview < aspectRatioArray) {
- // The new width must be smaller than the height, so scale the width by AR
- cropH = activeArray.height();
- cropW = cropH * aspectRatioPreview;
- } else {
- // The new height must be smaller (or equal) than the width, so scale the height by AR
- cropW = activeArray.width();
- cropH = cropW / aspectRatioPreview;
- }
-
- Matrix translateMatrix = new Matrix();
- RectF cropRect = new RectF(/*left*/0, /*top*/0, cropW, cropH);
-
- // Now center the crop rectangle so its center is in the center of the active array
- translateMatrix.setTranslate(activeArray.exactCenterX(), activeArray.exactCenterY());
- translateMatrix.postTranslate(-cropRect.centerX(), -cropRect.centerY());
-
- translateMatrix.mapRect(/*inout*/cropRect);
-
- // Round the rect corners towards the nearest integer values
- return ParamsUtils.createRect(cropRect);
- }
-
- /**
- * Shrink the {@code shrinkTarget} rectangle to snugly fit inside of {@code reference};
- * the aspect ratio of {@code shrinkTarget} will change to be the same aspect ratio as
- * {@code reference}.
- *
- * <p>At most a single dimension will scale (down). Both dimensions will never be scaled.</p>
- *
- * @param reference the rectangle whose aspect ratio will be used as the new aspect ratio
- * @param shrinkTarget the rectangle which will be scaled down to have a new aspect ratio
- *
- * @return a new rectangle, a subset of {@code shrinkTarget},
- * whose aspect ratio will match that of {@code reference}
- */
- private static Rect shrinkToSameAspectRatioCentered(Rect reference, Rect shrinkTarget) {
- float aspectRatioReference = reference.width() * 1.0f / reference.height();
- float aspectRatioShrinkTarget = shrinkTarget.width() * 1.0f / shrinkTarget.height();
-
- float cropH, cropW;
- if (aspectRatioShrinkTarget < aspectRatioReference) {
- // The new width must be smaller than the height, so scale the width by AR
- cropH = reference.height();
- cropW = cropH * aspectRatioShrinkTarget;
- } else {
- // The new height must be smaller (or equal) than the width, so scale the height by AR
- cropW = reference.width();
- cropH = cropW / aspectRatioShrinkTarget;
- }
-
- Matrix translateMatrix = new Matrix();
- RectF shrunkRect = new RectF(shrinkTarget);
-
- // Scale the rectangle down, but keep its center in the same place as before
- translateMatrix.setScale(cropW / reference.width(), cropH / reference.height(),
- shrinkTarget.exactCenterX(), shrinkTarget.exactCenterY());
-
- translateMatrix.mapRect(/*inout*/shrunkRect);
-
- return ParamsUtils.createRect(shrunkRect);
- }
-
- /**
- * Get the available 'crop' (zoom) rectangles for this camera that will be reported
- * via a {@code CaptureResult} when a zoom is requested.
- *
- * <p>These crops ignores the underlying preview buffer size, and will always be reported
- * the same values regardless of what configuration of outputs is used.</p>
- *
- * <p>When zoom is supported, this will return a list of {@code 1 + #getMaxZoom} size,
- * where each crop rectangle corresponds to a zoom ratio (and is centered at the middle).</p>
- *
- * <p>Each crop rectangle is changed to have the same aspect ratio as {@code streamSize},
- * by shrinking the rectangle if necessary.</p>
- *
- * <p>To get the reported crop region when applying a zoom to the sensor, use {@code streamSize}
- * = {@code activeArray size}.</p>
- *
- * @param params non-{@code null} camera api1 parameters
- * @param activeArray active array dimensions, in sensor space
- * @param streamSize stream size dimensions, in pixels
- *
- * @return a list of available zoom rectangles, sorted from least zoomed to most zoomed
- */
- public static List<Rect> getAvailableZoomCropRectangles(
- Camera.Parameters params, Rect activeArray) {
- checkNotNull(params, "params must not be null");
- checkNotNull(activeArray, "activeArray must not be null");
-
- return getAvailableCropRectangles(params, activeArray, ParamsUtils.createSize(activeArray));
- }
-
- /**
- * Get the available 'crop' (zoom) rectangles for this camera.
- *
- * <p>This is the effective (real) crop that is applied by the camera api1 device
- * when projecting the zoom onto the intermediate preview buffer. Use this when
- * deciding which zoom ratio to apply.</p>
- *
- * <p>When zoom is supported, this will return a list of {@code 1 + #getMaxZoom} size,
- * where each crop rectangle corresponds to a zoom ratio (and is centered at the middle).</p>
- *
- * <p>Each crop rectangle is changed to have the same aspect ratio as {@code streamSize},
- * by shrinking the rectangle if necessary.</p>
- *
- * <p>To get the reported crop region when applying a zoom to the sensor, use {@code streamSize}
- * = {@code activeArray size}.</p>
- *
- * @param params non-{@code null} camera api1 parameters
- * @param activeArray active array dimensions, in sensor space
- * @param streamSize stream size dimensions, in pixels
- *
- * @return a list of available zoom rectangles, sorted from least zoomed to most zoomed
- */
- public static List<Rect> getAvailablePreviewZoomCropRectangles(Camera.Parameters params,
- Rect activeArray, Size previewSize) {
- checkNotNull(params, "params must not be null");
- checkNotNull(activeArray, "activeArray must not be null");
- checkNotNull(previewSize, "previewSize must not be null");
-
- return getAvailableCropRectangles(params, activeArray, previewSize);
- }
-
- /**
- * Get the available 'crop' (zoom) rectangles for this camera.
- *
- * <p>When zoom is supported, this will return a list of {@code 1 + #getMaxZoom} size,
- * where each crop rectangle corresponds to a zoom ratio (and is centered at the middle).</p>
- *
- * <p>Each crop rectangle is changed to have the same aspect ratio as {@code streamSize},
- * by shrinking the rectangle if necessary.</p>
- *
- * <p>To get the reported crop region when applying a zoom to the sensor, use {@code streamSize}
- * = {@code activeArray size}.</p>
- *
- * @param params non-{@code null} camera api1 parameters
- * @param activeArray active array dimensions, in sensor space
- * @param streamSize stream size dimensions, in pixels
- *
- * @return a list of available zoom rectangles, sorted from least zoomed to most zoomed
- */
- private static List<Rect> getAvailableCropRectangles(Camera.Parameters params,
- Rect activeArray, Size streamSize) {
- checkNotNull(params, "params must not be null");
- checkNotNull(activeArray, "activeArray must not be null");
- checkNotNull(streamSize, "streamSize must not be null");
-
- // TODO: change all uses of Rect activeArray to Size activeArray,
- // since we want the crop to be active-array relative, not pixel-array relative
-
- Rect unzoomedStreamCrop = getPreviewCropRectangleUnzoomed(activeArray, streamSize);
-
- if (!params.isZoomSupported()) {
- // Trivial case: No zoom -> only support the full size as the crop region
- return new ArrayList<>(Arrays.asList(unzoomedStreamCrop));
- }
-
- List<Rect> zoomCropRectangles = new ArrayList<>(params.getMaxZoom() + 1);
- Matrix scaleMatrix = new Matrix();
- RectF scaledRect = new RectF();
-
- for (int zoom : params.getZoomRatios()) {
- float shrinkRatio = ZOOM_RATIO_MULTIPLIER * 1.0f / zoom; // normalize to 1.0 and smaller
-
- // set scaledRect to unzoomedStreamCrop
- ParamsUtils.convertRectF(unzoomedStreamCrop, /*out*/scaledRect);
-
- scaleMatrix.setScale(
- shrinkRatio, shrinkRatio,
- activeArray.exactCenterX(),
- activeArray.exactCenterY());
-
- scaleMatrix.mapRect(scaledRect);
-
- Rect intRect = ParamsUtils.createRect(scaledRect);
-
- // Round the rect corners towards the nearest integer values
- zoomCropRectangles.add(intRect);
- }
-
- return zoomCropRectangles;
- }
-
- /**
- * Get the largest possible zoom ratio (normalized to {@code 1.0f} and higher)
- * that the camera can support.
- *
- * <p>If the camera does not support zoom, it always returns {@code 1.0f}.</p>
- *
- * @param params non-{@code null} camera api1 parameters
- * @return normalized max zoom ratio, at least {@code 1.0f}
- */
- public static float getMaxZoomRatio(Camera.Parameters params) {
- if (!params.isZoomSupported()) {
- return 1.0f; // no zoom
- }
-
- List<Integer> zoomRatios = params.getZoomRatios(); // sorted smallest->largest
- int zoom = zoomRatios.get(zoomRatios.size() - 1); // largest zoom ratio
- float zoomRatio = zoom * 1.0f / ZOOM_RATIO_MULTIPLIER; // normalize to 1.0 and smaller
-
- return zoomRatio;
- }
-
- /**
- * Returns the component-wise zoom ratio (each greater or equal than {@code 1.0});
- * largest values means more zoom.
- *
- * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
- * @param cropSize size of the crop/zoom
- *
- * @return {@link SizeF} with width/height being the component-wise zoom ratio
- *
- * @throws NullPointerException if any of the args were {@code null}
- * @throws IllegalArgumentException if any component of {@code cropSize} was {@code 0}
- */
- private static SizeF getZoomRatio(Size activeArraySize, Size cropSize) {
- checkNotNull(activeArraySize, "activeArraySize must not be null");
- checkNotNull(cropSize, "cropSize must not be null");
- checkArgumentPositive(cropSize.getWidth(), "cropSize.width must be positive");
- checkArgumentPositive(cropSize.getHeight(), "cropSize.height must be positive");
-
- float zoomRatioWidth = activeArraySize.getWidth() * 1.0f / cropSize.getWidth();
- float zoomRatioHeight = activeArraySize.getHeight() * 1.0f / cropSize.getHeight();
-
- return new SizeF(zoomRatioWidth, zoomRatioHeight);
- }
-
- /**
- * Convert the user-specified crop region/zoom into zoom data; which can be used
- * to set the parameters to a specific zoom index, or to report back to the user what
- * the actual zoom was, or for other calculations requiring the current preview crop region.
- *
- * <p>None of the parameters are mutated.<p>
- *
- * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
- * @param cropRegion the user-specified crop region
- * @param zoomRatio the user-specified zoom ratio
- * @param previewSize the current preview size (in pixels)
- * @param params the current camera parameters (not mutated)
- *
- * @return the zoom index, and the effective/reported crop regions (relative to active array)
- */
- public static ZoomData convertToLegacyZoom(Rect activeArraySize, Rect
- cropRegion, Float zoomRatio, Size previewSize, Camera.Parameters params) {
- final float FLOAT_EQUAL_THRESHOLD = 0.0001f;
- if (zoomRatio != null &&
- Math.abs(1.0f - zoomRatio) > FLOAT_EQUAL_THRESHOLD) {
- // User uses CONTROL_ZOOM_RATIO to control zoom
- return convertZoomRatio(activeArraySize, zoomRatio, previewSize, params);
- }
-
- return convertScalerCropRegion(activeArraySize, cropRegion, previewSize, params);
- }
-
- /**
- * Convert the user-specified zoom ratio into zoom data; which can be used
- * to set the parameters to a specific zoom index, or to report back to the user what the
- * actual zoom was, or for other calculations requiring the current preview crop region.
- *
- * <p>None of the parameters are mutated.</p>
- *
- * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
- * @param zoomRatio the current zoom ratio
- * @param previewSize the current preview size (in pixels)
- * @param params the current camera parameters (not mutated)
- *
- * @return the zoom index, and the effective/reported crop regions (relative to active array)
- */
- public static ZoomData convertZoomRatio(Rect activeArraySize, float zoomRatio,
- Size previewSize, Camera.Parameters params) {
- if (DEBUG) {
- Log.v(TAG, "convertZoomRatio - user zoom ratio was " + zoomRatio);
- }
-
- List<Rect> availableReportedCropRegions =
- getAvailableZoomCropRectangles(params, activeArraySize);
- List<Rect> availablePreviewCropRegions =
- getAvailablePreviewZoomCropRectangles(params, activeArraySize, previewSize);
- if (availableReportedCropRegions.size() != availablePreviewCropRegions.size()) {
- throw new AssertionError("available reported/preview crop region size mismatch");
- }
-
- // Find the best matched legacy zoom ratio for the requested camera2 zoom ratio.
- int bestZoomIndex = 0;
- Rect reportedCropRegion = new Rect(availableReportedCropRegions.get(0));
- Rect previewCropRegion = new Rect(availablePreviewCropRegions.get(0));
- float reportedZoomRatio = 1.0f;
- if (params.isZoomSupported()) {
- List<Integer> zoomRatios = params.getZoomRatios();
- for (int i = 1; i < zoomRatios.size(); i++) {
- if (zoomRatio * ZOOM_RATIO_MULTIPLIER >= zoomRatios.get(i)) {
- bestZoomIndex = i;
- reportedCropRegion = availableReportedCropRegions.get(i);
- previewCropRegion = availablePreviewCropRegions.get(i);
- reportedZoomRatio = zoomRatios.get(i);
- } else {
- break;
- }
- }
- }
-
- if (DEBUG) {
- Log.v(TAG, "convertZoomRatio - zoom calculated to: " +
- "zoomIndex = " + bestZoomIndex +
- ", reported crop region = " + reportedCropRegion +
- ", preview crop region = " + previewCropRegion +
- ", reported zoom ratio = " + reportedZoomRatio);
- }
-
- return new ZoomData(bestZoomIndex, reportedCropRegion,
- previewCropRegion, reportedZoomRatio);
- }
-
- /**
- * Convert the user-specified crop region into zoom data; which can be used
- * to set the parameters to a specific zoom index, or to report back to the user what the
- * actual zoom was, or for other calculations requiring the current preview crop region.
- *
- * <p>None of the parameters are mutated.</p>
- *
- * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
- * @param cropRegion the user-specified crop region
- * @param previewSize the current preview size (in pixels)
- * @param params the current camera parameters (not mutated)
- *
- * @return the zoom index, and the effective/reported crop regions (relative to active array)
- */
- public static ZoomData convertScalerCropRegion(Rect activeArraySize, Rect
- cropRegion, Size previewSize, Camera.Parameters params) {
- Rect activeArraySizeOnly = new Rect(
- /*left*/0, /*top*/0,
- activeArraySize.width(), activeArraySize.height());
-
- Rect userCropRegion = cropRegion;
-
- if (userCropRegion == null) {
- userCropRegion = activeArraySizeOnly;
- }
-
- if (DEBUG) {
- Log.v(TAG, "convertScalerCropRegion - user crop region was " + userCropRegion);
- }
-
- final Rect reportedCropRegion = new Rect();
- final Rect previewCropRegion = new Rect();
- final int zoomIdx = ParameterUtils.getClosestAvailableZoomCrop(params, activeArraySizeOnly,
- previewSize, userCropRegion,
- /*out*/reportedCropRegion, /*out*/previewCropRegion);
- final float reportedZoomRatio = 1.0f;
-
- if (DEBUG) {
- Log.v(TAG, "convertScalerCropRegion - zoom calculated to: " +
- "zoomIndex = " + zoomIdx +
- ", reported crop region = " + reportedCropRegion +
- ", preview crop region = " + previewCropRegion +
- ", reported zoom ratio = " + reportedZoomRatio);
- }
-
- return new ZoomData(zoomIdx, previewCropRegion, reportedCropRegion, reportedZoomRatio);
- }
-
- /**
- * Calculate the actual/effective/reported normalized rectangle data from a metering
- * rectangle.
- *
- * <p>If any of the rectangles are out-of-range of their intended bounding box,
- * the {@link #RECTANGLE_EMPTY empty rectangle} is substituted instead
- * (with a weight of {@code 0}).</p>
- *
- * <p>The metering rectangle is bound by the crop region (effective/reported respectively).
- * The metering {@link Camera.Area area} is bound by {@code [-1000, 1000]}.</p>
- *
- * <p>No parameters are mutated; returns the new metering data.</p>
- *
- * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
- * @param meteringRect the user-specified metering rectangle
- * @param zoomData the calculated zoom data corresponding to this request
- *
- * @return the metering area, the reported/effective metering rectangles
- */
- public static MeteringData convertMeteringRectangleToLegacy(
- Rect activeArray, MeteringRectangle meteringRect, ZoomData zoomData) {
- Rect previewCrop = zoomData.previewCrop;
-
- float scaleW = (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN) * 1.0f /
- previewCrop.width();
- float scaleH = (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN) * 1.0f /
- previewCrop.height();
-
- Matrix transform = new Matrix();
- // Move the preview crop so that top,left is at (0,0), otherwise after scaling
- // the corner bounds will be outside of [-1000, 1000]
- transform.setTranslate(-previewCrop.left, -previewCrop.top);
- // Scale into [0, 2000] range about the center of the preview
- transform.postScale(scaleW, scaleH);
- // Move so that top left of a typical rect is at [-1000, -1000]
- transform.postTranslate(/*dx*/NORMALIZED_RECTANGLE_MIN, /*dy*/NORMALIZED_RECTANGLE_MIN);
-
- /*
- * Calculate the preview metering region (effective), and the camera1 api
- * normalized metering region.
- */
- Rect normalizedRegionUnbounded = ParamsUtils.mapRect(transform, meteringRect.getRect());
-
- /*
- * Try to intersect normalized area with [-1000, 1000] rectangle; otherwise
- * it's completely out of range
- */
- Rect normalizedIntersected = new Rect(normalizedRegionUnbounded);
-
- Camera.Area meteringArea;
- if (!normalizedIntersected.intersect(NORMALIZED_RECTANGLE_DEFAULT)) {
- Log.w(TAG,
- "convertMeteringRectangleToLegacy - metering rectangle too small, " +
- "no metering will be done");
- normalizedIntersected.set(RECTANGLE_EMPTY);
- meteringArea = new Camera.Area(RECTANGLE_EMPTY,
- MeteringRectangle.METERING_WEIGHT_DONT_CARE);
- } else {
- meteringArea = new Camera.Area(normalizedIntersected,
- meteringRect.getMeteringWeight());
- }
-
- /*
- * Calculate effective preview metering region
- */
- Rect previewMetering = meteringRect.getRect();
- if (!previewMetering.intersect(previewCrop)) {
- previewMetering.set(RECTANGLE_EMPTY);
- }
-
- /*
- * Calculate effective reported metering region
- * - Transform the calculated metering area back into active array space
- * - Clip it to be a subset of the reported crop region
- */
- Rect reportedMetering;
- {
- Camera.Area normalizedAreaUnbounded = new Camera.Area(
- normalizedRegionUnbounded, meteringRect.getMeteringWeight());
- WeightedRectangle reportedMeteringRect = convertCameraAreaToActiveArrayRectangle(
- activeArray, zoomData, normalizedAreaUnbounded, /*usePreviewCrop*/false);
- reportedMetering = reportedMeteringRect.rect;
- }
-
- if (DEBUG) {
- Log.v(TAG, String.format(
- "convertMeteringRectangleToLegacy - activeArray = %s, meteringRect = %s, " +
- "previewCrop = %s, meteringArea = %s, previewMetering = %s, " +
- "reportedMetering = %s, normalizedRegionUnbounded = %s",
- activeArray, meteringRect,
- previewCrop, stringFromArea(meteringArea), previewMetering,
- reportedMetering, normalizedRegionUnbounded));
- }
-
- return new MeteringData(meteringArea, previewMetering, reportedMetering);
- }
-
- /**
- * Convert the normalized camera area from [-1000, 1000] coordinate space
- * into the active array-based coordinate space.
- *
- * <p>Values out of range are clipped to be within the resulting (reported) crop
- * region. It is possible to have values larger than the preview crop.</p>
- *
- * <p>Weights out of range of [0, 1000] are clipped to be within the range.</p>
- *
- * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
- * @param zoomData the calculated zoom data corresponding to this request
- * @param area the normalized camera area
- *
- * @return the weighed rectangle in active array coordinate space, with the weight
- */
- public static WeightedRectangle convertCameraAreaToActiveArrayRectangle(
- Rect activeArray, ZoomData zoomData, Camera.Area area) {
- return convertCameraAreaToActiveArrayRectangle(activeArray, zoomData, area,
- /*usePreviewCrop*/true);
- }
-
- /**
- * Convert an api1 face into an active-array based api2 face.
- *
- * <p>Out-of-ranges scores and ids will be clipped to be within range (with a warning).</p>
- *
- * @param face a non-{@code null} api1 face
- * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
- * @param zoomData the calculated zoom data corresponding to this request
- *
- * @return a non-{@code null} api2 face
- *
- * @throws NullPointerException if the {@code face} was {@code null}
- */
- public static Face convertFaceFromLegacy(Camera.Face face, Rect activeArray,
- ZoomData zoomData) {
- checkNotNull(face, "face must not be null");
-
- Face api2Face;
-
- Camera.Area fakeArea = new Camera.Area(face.rect, /*weight*/1);
-
- WeightedRectangle faceRect =
- convertCameraAreaToActiveArrayRectangle(activeArray, zoomData, fakeArea);
-
- Point leftEye = face.leftEye, rightEye = face.rightEye, mouth = face.mouth;
- if (leftEye != null && rightEye != null && mouth != null && leftEye.x != -2000 &&
- leftEye.y != -2000 && rightEye.x != -2000 && rightEye.y != -2000 &&
- mouth.x != -2000 && mouth.y != -2000) {
- leftEye = convertCameraPointToActiveArrayPoint(activeArray, zoomData,
- leftEye, /*usePreviewCrop*/true);
- rightEye = convertCameraPointToActiveArrayPoint(activeArray, zoomData,
- leftEye, /*usePreviewCrop*/true);
- mouth = convertCameraPointToActiveArrayPoint(activeArray, zoomData,
- leftEye, /*usePreviewCrop*/true);
-
- api2Face = faceRect.toFace(face.id, leftEye, rightEye, mouth);
- } else {
- api2Face = faceRect.toFace();
- }
-
- return api2Face;
- }
-
- private static Point convertCameraPointToActiveArrayPoint(
- Rect activeArray, ZoomData zoomData, Point point, boolean usePreviewCrop) {
- Rect pointedRect = new Rect(point.x, point.y, point.x, point.y);
- Camera.Area pointedArea = new Area(pointedRect, /*weight*/1);
-
- WeightedRectangle adjustedRect =
- convertCameraAreaToActiveArrayRectangle(activeArray,
- zoomData, pointedArea, usePreviewCrop);
-
- Point transformedPoint = new Point(adjustedRect.rect.left, adjustedRect.rect.top);
-
- return transformedPoint;
- }
-
- private static WeightedRectangle convertCameraAreaToActiveArrayRectangle(
- Rect activeArray, ZoomData zoomData, Camera.Area area, boolean usePreviewCrop) {
- Rect previewCrop = zoomData.previewCrop;
- Rect reportedCrop = zoomData.reportedCrop;
-
- float scaleW = previewCrop.width() * 1.0f /
- (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN);
- float scaleH = previewCrop.height() * 1.0f /
- (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN);
-
- /*
- * Calculate the reported metering region from the non-intersected normalized region
- * by scaling and translating back into active array-relative coordinates.
- */
- Matrix transform = new Matrix();
-
- // Move top left from (-1000, -1000) to (0, 0)
- transform.setTranslate(/*dx*/NORMALIZED_RECTANGLE_MAX, /*dy*/NORMALIZED_RECTANGLE_MAX);
-
- // Scale from [0, 2000] back into the preview rectangle
- transform.postScale(scaleW, scaleH);
-
- // Move the rect so that the [-1000,-1000] point ends up at the preview [left, top]
- transform.postTranslate(previewCrop.left, previewCrop.top);
-
- Rect cropToIntersectAgainst = usePreviewCrop ? previewCrop : reportedCrop;
-
- // Now apply the transformation backwards to get the reported metering region
- Rect reportedMetering = ParamsUtils.mapRect(transform, area.rect);
- // Intersect it with the crop region, to avoid reporting out-of-bounds
- // metering regions
- if (!reportedMetering.intersect(cropToIntersectAgainst)) {
- reportedMetering.set(RECTANGLE_EMPTY);
- }
-
- int weight = area.weight;
- if (weight < MeteringRectangle.METERING_WEIGHT_MIN) {
- Log.w(TAG,
- "convertCameraAreaToMeteringRectangle - rectangle "
- + stringFromArea(area) + " has too small weight, clip to 0");
- weight = 0;
- }
-
- return new WeightedRectangle(reportedMetering, area.weight);
- }
-
-
- private ParameterUtils() {
- throw new AssertionError();
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/PerfMeasurement.java b/core/java/android/hardware/camera2/legacy/PerfMeasurement.java
deleted file mode 100644
index 53278c7..0000000
--- a/core/java/android/hardware/camera2/legacy/PerfMeasurement.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.io.BufferedWriter;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.Queue;
-
-/**
- * GPU and CPU performance measurement for the legacy implementation.
- *
- * <p>Measures CPU and GPU processing duration for a set of operations, and dumps
- * the results into a file.</p>
- *
- * <p>Rough usage:
- * <pre>
- * {@code
- * <set up workload>
- * <start long-running workload>
- * mPerfMeasurement.startTimer();
- * ...render a frame...
- * mPerfMeasurement.stopTimer();
- * <end workload>
- * mPerfMeasurement.dumpPerformanceData("/sdcard/my_data.txt");
- * }
- * </pre>
- * </p>
- *
- * <p>All calls to this object must be made within the same thread, and the same GL context.
- * PerfMeasurement cannot be used outside of a GL context. The only exception is
- * dumpPerformanceData, which can be called outside of a valid GL context.</p>
- */
-class PerfMeasurement {
- private static final String TAG = "PerfMeasurement";
-
- public static final int DEFAULT_MAX_QUERIES = 3;
-
- private final long mNativeContext;
-
- private int mCompletedQueryCount = 0;
-
- /**
- * Values for completed measurements
- */
- private ArrayList<Long> mCollectedGpuDurations = new ArrayList<>();
- private ArrayList<Long> mCollectedCpuDurations = new ArrayList<>();
- private ArrayList<Long> mCollectedTimestamps = new ArrayList<>();
-
- /**
- * Values for in-progress measurements (waiting for async GPU results)
- */
- private Queue<Long> mTimestampQueue = new LinkedList<>();
- private Queue<Long> mCpuDurationsQueue = new LinkedList<>();
-
- private long mStartTimeNs;
-
- /**
- * The value returned by {@link #nativeGetNextGlDuration} if no new timing
- * measurement is available since the last call.
- */
- private static final long NO_DURATION_YET = -1l;
-
- /**
- * The value returned by {@link #nativeGetNextGlDuration} if timing failed for
- * the next timing interval
- */
- private static final long FAILED_TIMING = -2l;
-
- /**
- * Create a performance measurement object with a maximum of {@value #DEFAULT_MAX_QUERIES}
- * in-progess queries.
- */
- public PerfMeasurement() {
- mNativeContext = nativeCreateContext(DEFAULT_MAX_QUERIES);
- }
-
- /**
- * Create a performance measurement object with maxQueries as the maximum number of
- * in-progress queries.
- *
- * @param maxQueries maximum in-progress queries, must be larger than 0.
- * @throws IllegalArgumentException if maxQueries is less than 1.
- */
- public PerfMeasurement(int maxQueries) {
- if (maxQueries < 1) throw new IllegalArgumentException("maxQueries is less than 1");
- mNativeContext = nativeCreateContext(maxQueries);
- }
-
- /**
- * Returns true if the Gl timing methods will work, false otherwise.
- *
- * <p>Must be called within a valid GL context.</p>
- */
- public static boolean isGlTimingSupported() {
- return nativeQuerySupport();
- }
-
- /**
- * Dump collected data to file, and clear the stored data.
- *
- * <p>
- * Format is a simple csv-like text file with a header,
- * followed by a 3-column list of values in nanoseconds:
- * <pre>
- * timestamp gpu_duration cpu_duration
- * <long> <long> <long>
- * <long> <long> <long>
- * <long> <long> <long>
- * ....
- * </pre>
- * </p>
- */
- public void dumpPerformanceData(String path) {
- try (BufferedWriter dump = new BufferedWriter(new FileWriter(path))) {
- dump.write("timestamp gpu_duration cpu_duration\n");
- for (int i = 0; i < mCollectedGpuDurations.size(); i++) {
- dump.write(String.format("%d %d %d\n",
- mCollectedTimestamps.get(i),
- mCollectedGpuDurations.get(i),
- mCollectedCpuDurations.get(i)));
- }
- mCollectedTimestamps.clear();
- mCollectedGpuDurations.clear();
- mCollectedCpuDurations.clear();
- } catch (IOException e) {
- Log.e(TAG, "Error writing data dump to " + path + ":" + e);
- }
- }
-
- /**
- * Start a GPU/CPU timing measurement.
- *
- * <p>Call before starting a rendering pass. Only one timing measurement can be active at once,
- * so {@link #stopTimer} must be called before the next call to this method.</p>
- *
- * @throws IllegalStateException if the maximum number of queries are in progress already,
- * or the method is called multiple times in a row, or there is
- * a GPU error.
- */
- public void startTimer() {
- nativeStartGlTimer(mNativeContext);
- mStartTimeNs = SystemClock.elapsedRealtimeNanos();
- }
-
- /**
- * Finish a GPU/CPU timing measurement.
- *
- * <p>Call after finishing all the drawing for a rendering pass. Only one timing measurement can
- * be active at once, so {@link #startTimer} must be called before the next call to this
- * method.</p>
- *
- * @throws IllegalStateException if no GL timer is currently started, or there is a GPU
- * error.
- */
- public void stopTimer() {
- // Complete CPU timing
- long endTimeNs = SystemClock.elapsedRealtimeNanos();
- mCpuDurationsQueue.add(endTimeNs - mStartTimeNs);
- // Complete GL timing
- nativeStopGlTimer(mNativeContext);
-
- // Poll to see if GL timing results have arrived; if so
- // store the results for a frame
- long duration = getNextGlDuration();
- if (duration > 0) {
- mCollectedGpuDurations.add(duration);
- mCollectedTimestamps.add(mTimestampQueue.isEmpty() ?
- NO_DURATION_YET : mTimestampQueue.poll());
- mCollectedCpuDurations.add(mCpuDurationsQueue.isEmpty() ?
- NO_DURATION_YET : mCpuDurationsQueue.poll());
- }
- if (duration == FAILED_TIMING) {
- // Discard timestamp and CPU measurement since GPU measurement failed
- if (!mTimestampQueue.isEmpty()) {
- mTimestampQueue.poll();
- }
- if (!mCpuDurationsQueue.isEmpty()) {
- mCpuDurationsQueue.poll();
- }
- }
- }
-
- /**
- * Add a timestamp to a timing measurement. These are queued up and matched to completed
- * workload measurements as they become available.
- */
- public void addTimestamp(long timestamp) {
- mTimestampQueue.add(timestamp);
- }
-
- /**
- * Get the next available GPU timing measurement.
- *
- * <p>Since the GPU works asynchronously, the results of a single start/stopGlTimer measurement
- * will only be available some time after the {@link #stopTimer} call is made. Poll this method
- * until the result becomes available. If multiple start/endTimer measurements are made in a
- * row, the results will be available in FIFO order.</p>
- *
- * @return The measured duration of the GPU workload for the next pending query, or
- * {@link #NO_DURATION_YET} if no queries are pending or the next pending query has not
- * yet finished, or {@link #FAILED_TIMING} if the GPU was unable to complete the
- * measurement.
- *
- * @throws IllegalStateException If there is a GPU error.
- *
- */
- private long getNextGlDuration() {
- long duration = nativeGetNextGlDuration(mNativeContext);
- if (duration > 0) {
- mCompletedQueryCount++;
- }
- return duration;
- }
-
- /**
- * Returns the number of measurements so far that returned a valid duration
- * measurement.
- */
- public int getCompletedQueryCount() {
- return mCompletedQueryCount;
- }
-
- @Override
- protected void finalize() {
- nativeDeleteContext(mNativeContext);
- }
-
- /**
- * Create a native performance measurement context.
- *
- * @param maxQueryCount maximum in-progress queries; must be >= 1.
- */
- private static native long nativeCreateContext(int maxQueryCount);
-
- /**
- * Delete the native context.
- *
- * <p>Not safe to call more than once.</p>
- */
- private static native void nativeDeleteContext(long contextHandle);
-
- /**
- * Query whether the relevant Gl extensions are available for Gl timing
- */
- private static native boolean nativeQuerySupport();
-
- /**
- * Start a GL timing section.
- *
- * <p>All GL commands between this method and the next {@link #nativeEndGlTimer} will be
- * included in the timing.</p>
- *
- * <p>Must be called from the same thread as calls to {@link #nativeEndGlTimer} and
- * {@link #nativeGetNextGlDuration}.</p>
- *
- * @throws IllegalStateException if a GL error occurs or start is called repeatedly.
- */
- protected static native void nativeStartGlTimer(long contextHandle);
-
- /**
- * Finish a GL timing section.
- *
- * <p>Some time after this call returns, the time the GPU took to
- * execute all work submitted between the latest {@link #nativeStartGlTimer} and
- * this call, will become available from calling {@link #nativeGetNextGlDuration}.</p>
- *
- * <p>Must be called from the same thread as calls to {@link #nativeStartGlTimer} and
- * {@link #nativeGetNextGlDuration}.</p>
- *
- * @throws IllegalStateException if a GL error occurs or stop is called before start
- */
- protected static native void nativeStopGlTimer(long contextHandle);
-
- /**
- * Get the next available GL duration measurement, in nanoseconds.
- *
- * <p>Must be called from the same thread as calls to {@link #nativeStartGlTimer} and
- * {@link #nativeEndGlTimer}.</p>
- *
- * @return the next GL duration measurement, or {@link #NO_DURATION_YET} if
- * no new measurement is available, or {@link #FAILED_TIMING} if timing
- * failed for the next duration measurement.
- * @throws IllegalStateException if a GL error occurs
- */
- protected static native long nativeGetNextGlDuration(long contextHandle);
-
-
-}
diff --git a/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java b/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
deleted file mode 100644
index e19ebf2..0000000
--- a/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.MessageQueue;
-
-public class RequestHandlerThread extends HandlerThread {
-
- /**
- * Ensure that the MessageQueue's idle handler gets run by poking the message queue;
- * normally if the message queue is already idle, the idle handler won't get invoked.
- *
- * <p>Users of this handler thread should ignore this message.</p>
- */
- public final static int MSG_POKE_IDLE_HANDLER = -1;
-
- private final ConditionVariable mStarted = new ConditionVariable(false);
- private final ConditionVariable mIdle = new ConditionVariable(true);
- private Handler.Callback mCallback;
- private volatile Handler mHandler;
-
- public RequestHandlerThread(String name, Handler.Callback callback) {
- super(name, Thread.MAX_PRIORITY);
- mCallback = callback;
- }
-
- @Override
- protected void onLooperPrepared() {
- mHandler = new Handler(getLooper(), mCallback);
- mStarted.open();
- }
-
- // Blocks until thread has started
- public void waitUntilStarted() {
- mStarted.block();
- }
-
- // May return null if the handler is not set up yet.
- public Handler getHandler() {
- return mHandler;
- }
-
- // Blocks until thread has started
- public Handler waitAndGetHandler() {
- waitUntilStarted();
- return getHandler();
- }
-
- // Atomic multi-type message existence check
- public boolean hasAnyMessages(int[] what) {
- synchronized (mHandler.getLooper().getQueue()) {
- for (int i : what) {
- if (mHandler.hasMessages(i)) {
- return true;
- }
- }
- }
- return false;
- }
-
- // Atomic multi-type message remove
- public void removeMessages(int[] what) {
- synchronized (mHandler.getLooper().getQueue()) {
- for (int i : what) {
- mHandler.removeMessages(i);
- }
- }
- }
-
- private final MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() {
- @Override
- public boolean queueIdle() {
- mIdle.open();
- return false;
- }
- };
-
- // Blocks until thread is idling
- public void waitUntilIdle() {
- Handler handler = waitAndGetHandler();
- MessageQueue queue = handler.getLooper().getQueue();
- if (queue.isIdle()) {
- return;
- }
- mIdle.close();
- queue.addIdleHandler(mIdleHandler);
- // Ensure that the idle handler gets run even if the looper already went idle
- handler.sendEmptyMessage(MSG_POKE_IDLE_HANDLER);
- if (queue.isIdle()) {
- return;
- }
- mIdle.block();
- }
-
-}
diff --git a/core/java/android/hardware/camera2/legacy/RequestHolder.java b/core/java/android/hardware/camera2/legacy/RequestHolder.java
deleted file mode 100644
index 98b761b..0000000
--- a/core/java/android/hardware/camera2/legacy/RequestHolder.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.hardware.camera2.CaptureRequest;
-import android.util.Log;
-import android.view.Surface;
-
-import java.util.Collection;
-
-import static com.android.internal.util.Preconditions.*;
-
-/**
- * Semi-immutable container for a single capture request and associated information,
- * the only mutable characteristic of this container is whether or not is has been
- * marked as "failed" using {@code #failRequest}.
- */
-public class RequestHolder {
- private static final String TAG = "RequestHolder";
-
- private final boolean mRepeating;
- private final CaptureRequest mRequest;
- private final int mRequestId;
- private final int mSubsequeceId;
- private final long mFrameNumber;
- private final int mNumJpegTargets;
- private final int mNumPreviewTargets;
- private volatile boolean mFailed = false;
- private boolean mOutputAbandoned = false;
-
- private final Collection<Long> mJpegSurfaceIds;
-
- /**
- * A builder class for {@link RequestHolder} objects.
- *
- * <p>
- * This allows per-request queries to be cached for repeating {@link CaptureRequest} objects.
- * </p>
- */
- public final static class Builder {
- private final int mRequestId;
- private final int mSubsequenceId;
- private final CaptureRequest mRequest;
- private final boolean mRepeating;
- private final int mNumJpegTargets;
- private final int mNumPreviewTargets;
- private final Collection<Long> mJpegSurfaceIds;
-
- /**
- * Construct a new {@link Builder} to generate {@link RequestHolder} objects.
- *
- * @param requestId the ID to set in {@link RequestHolder} objects.
- * @param subsequenceId the sequence ID to set in {@link RequestHolder} objects.
- * @param request the original {@link CaptureRequest} to set in {@link RequestHolder}
- * objects.
- * @param repeating {@code true} if the request is repeating.
- */
- public Builder(int requestId, int subsequenceId, CaptureRequest request,
- boolean repeating, Collection<Long> jpegSurfaceIds) {
- checkNotNull(request, "request must not be null");
- mRequestId = requestId;
- mSubsequenceId = subsequenceId;
- mRequest = request;
- mRepeating = repeating;
- mJpegSurfaceIds = jpegSurfaceIds;
- mNumJpegTargets = numJpegTargets(mRequest);
- mNumPreviewTargets = numPreviewTargets(mRequest);
- }
-
- /**
- * Returns true if the given surface requires jpeg buffers.
- *
- * @param s a {@link android.view.Surface} to check.
- * @return true if the surface requires a jpeg buffer.
- */
- private boolean jpegType(Surface s)
- throws LegacyExceptionUtils.BufferQueueAbandonedException {
- return LegacyCameraDevice.containsSurfaceId(s, mJpegSurfaceIds);
- }
-
- /**
- * Returns true if the given surface requires non-jpeg buffer types.
- *
- * <p>
- * "Jpeg buffer" refers to the buffers returned in the jpeg
- * {@link android.hardware.Camera.PictureCallback}. Non-jpeg buffers are created using a tee
- * of the preview stream drawn to the surface
- * set via {@link android.hardware.Camera#setPreviewDisplay(android.view.SurfaceHolder)} or
- * equivalent methods.
- * </p>
- * @param s a {@link android.view.Surface} to check.
- * @return true if the surface requires a non-jpeg buffer type.
- */
- private boolean previewType(Surface s)
- throws LegacyExceptionUtils.BufferQueueAbandonedException {
- return !jpegType(s);
- }
-
- /**
- * Returns the number of surfaces targeted by the request that require jpeg buffers.
- */
- private int numJpegTargets(CaptureRequest request) {
- int count = 0;
- for (Surface s : request.getTargets()) {
- try {
- if (jpegType(s)) {
- ++count;
- }
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.d(TAG, "Surface abandoned, skipping...", e);
- }
- }
- return count;
- }
-
- /**
- * Returns the number of surfaces targeted by the request that require non-jpeg buffers.
- */
- private int numPreviewTargets(CaptureRequest request) {
- int count = 0;
- for (Surface s : request.getTargets()) {
- try {
- if (previewType(s)) {
- ++count;
- }
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.d(TAG, "Surface abandoned, skipping...", e);
- }
- }
- return count;
- }
-
- /**
- * Build a new {@link RequestHolder} using with parameters generated from this
- * {@link Builder}.
- *
- * @param frameNumber the {@code framenumber} to generate in the {@link RequestHolder}.
- * @return a {@link RequestHolder} constructed with the {@link Builder}'s parameters.
- */
- public RequestHolder build(long frameNumber) {
- return new RequestHolder(mRequestId, mSubsequenceId, mRequest, mRepeating, frameNumber,
- mNumJpegTargets, mNumPreviewTargets, mJpegSurfaceIds);
- }
- }
-
- private RequestHolder(int requestId, int subsequenceId, CaptureRequest request,
- boolean repeating, long frameNumber, int numJpegTargets,
- int numPreviewTargets, Collection<Long> jpegSurfaceIds) {
- mRepeating = repeating;
- mRequest = request;
- mRequestId = requestId;
- mSubsequeceId = subsequenceId;
- mFrameNumber = frameNumber;
- mNumJpegTargets = numJpegTargets;
- mNumPreviewTargets = numPreviewTargets;
- mJpegSurfaceIds = jpegSurfaceIds;
- }
-
- /**
- * Return the request id for the contained {@link CaptureRequest}.
- */
- public int getRequestId() {
- return mRequestId;
- }
-
- /**
- * Returns true if the contained request is repeating.
- */
- public boolean isRepeating() {
- return mRepeating;
- }
-
- /**
- * Return the subsequence id for this request.
- */
- public int getSubsequeceId() {
- return mSubsequeceId;
- }
-
- /**
- * Returns the frame number for this request.
- */
- public long getFrameNumber() {
- return mFrameNumber;
- }
-
- /**
- * Returns the contained request.
- */
- public CaptureRequest getRequest() {
- return mRequest;
- }
-
- /**
- * Returns a read-only collection of the surfaces targeted by the contained request.
- */
- public Collection<Surface> getHolderTargets() {
- return getRequest().getTargets();
- }
-
- /**
- * Returns true if any of the surfaces targeted by the contained request require jpeg buffers.
- */
- public boolean hasJpegTargets() {
- return mNumJpegTargets > 0;
- }
-
- /**
- * Returns true if any of the surfaces targeted by the contained request require a
- * non-jpeg buffer type.
- */
- public boolean hasPreviewTargets(){
- return mNumPreviewTargets > 0;
- }
-
- /**
- * Return the number of jpeg-type surfaces targeted by this request.
- */
- public int numJpegTargets() {
- return mNumJpegTargets;
- }
-
- /**
- * Return the number of non-jpeg-type surfaces targeted by this request.
- */
- public int numPreviewTargets() {
- return mNumPreviewTargets;
- }
-
- /**
- * Returns true if the given surface requires jpeg buffers.
- *
- * @param s a {@link android.view.Surface} to check.
- * @return true if the surface requires a jpeg buffer.
- */
- public boolean jpegType(Surface s)
- throws LegacyExceptionUtils.BufferQueueAbandonedException {
- return LegacyCameraDevice.containsSurfaceId(s, mJpegSurfaceIds);
- }
-
- /**
- * Mark this request as failed.
- */
- public void failRequest() {
- Log.w(TAG, "Capture failed for request: " + getRequestId());
- mFailed = true;
- }
-
- /**
- * Return {@code true} if this request failed.
- */
- public boolean requestFailed() {
- return mFailed;
- }
-
- /**
- * Mark at least one of this request's output surfaces is abandoned.
- */
- public void setOutputAbandoned() {
- mOutputAbandoned = true;
- }
-
- /**
- * Return if any of this request's output surface is abandoned.
- */
- public boolean isOutputAbandoned() {
- return mOutputAbandoned;
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/RequestQueue.java b/core/java/android/hardware/camera2/legacy/RequestQueue.java
deleted file mode 100644
index fb44402..0000000
--- a/core/java/android/hardware/camera2/legacy/RequestQueue.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.utils.SubmitInfo;
-import android.util.Log;
-
-import java.util.ArrayDeque;
-import java.util.List;
-
-/**
- * A queue of bursts of requests.
- *
- * <p>This queue maintains the count of frames that have been produced, and is thread safe.</p>
- */
-public class RequestQueue {
- private static final String TAG = "RequestQueue";
-
- public static final long INVALID_FRAME = -1;
-
- private BurstHolder mRepeatingRequest = null;
- private final ArrayDeque<BurstHolder> mRequestQueue = new ArrayDeque<BurstHolder>();
-
- private long mCurrentFrameNumber = 0;
- private long mCurrentRepeatingFrameNumber = INVALID_FRAME;
- private int mCurrentRequestId = 0;
- private final List<Long> mJpegSurfaceIds;
-
- public final class RequestQueueEntry {
- private final BurstHolder mBurstHolder;
- private final Long mFrameNumber;
- private final boolean mQueueEmpty;
-
- public BurstHolder getBurstHolder() {
- return mBurstHolder;
- }
- public Long getFrameNumber() {
- return mFrameNumber;
- }
- public boolean isQueueEmpty() {
- return mQueueEmpty;
- }
-
- public RequestQueueEntry(BurstHolder burstHolder, Long frameNumber, boolean queueEmpty) {
- mBurstHolder = burstHolder;
- mFrameNumber = frameNumber;
- mQueueEmpty = queueEmpty;
- }
- }
-
- public RequestQueue(List<Long> jpegSurfaceIds) {
- mJpegSurfaceIds = jpegSurfaceIds;
- }
-
- /**
- * Return and remove the next burst on the queue.
- *
- * <p>If a repeating burst is returned, it will not be removed.</p>
- *
- * @return an entry containing the next burst, the current frame number, and flag about whether
- * request queue becomes empty. Null if no burst exists.
- */
- public synchronized RequestQueueEntry getNext() {
- BurstHolder next = mRequestQueue.poll();
- boolean queueEmptied = (next != null && mRequestQueue.size() == 0);
- if (next == null && mRepeatingRequest != null) {
- next = mRepeatingRequest;
- mCurrentRepeatingFrameNumber = mCurrentFrameNumber +
- next.getNumberOfRequests();
- }
-
- if (next == null) {
- return null;
- }
-
- RequestQueueEntry ret = new RequestQueueEntry(next, mCurrentFrameNumber, queueEmptied);
- mCurrentFrameNumber += next.getNumberOfRequests();
- return ret;
- }
-
- /**
- * Cancel a repeating request.
- *
- * @param requestId the id of the repeating request to cancel.
- * @return the last frame to be returned from the HAL for the given repeating request, or
- * {@code INVALID_FRAME} if none exists.
- */
- public synchronized long stopRepeating(int requestId) {
- long ret = INVALID_FRAME;
- if (mRepeatingRequest != null && mRepeatingRequest.getRequestId() == requestId) {
- mRepeatingRequest = null;
- ret = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME :
- mCurrentRepeatingFrameNumber - 1;
- mCurrentRepeatingFrameNumber = INVALID_FRAME;
- Log.i(TAG, "Repeating capture request cancelled.");
- } else {
- Log.e(TAG, "cancel failed: no repeating request exists for request id: " + requestId);
- }
- return ret;
- }
-
- /**
- * Cancel a repeating request.
- *
- * @return the last frame to be returned from the HAL for the given repeating request, or
- * {@code INVALID_FRAME} if none exists.
- */
- public synchronized long stopRepeating() {
- if (mRepeatingRequest == null) {
- Log.e(TAG, "cancel failed: no repeating request exists.");
- return INVALID_FRAME;
- }
- return stopRepeating(mRepeatingRequest.getRequestId());
- }
-
- /**
- * Add a the given burst to the queue.
- *
- * <p>If the burst is repeating, replace the current repeating burst.</p>
- *
- * @param requests the burst of requests to add to the queue.
- * @param repeating true if the burst is repeating.
- * @return the submission info, including the new request id, and the last frame number, which
- * contains either the frame number of the last frame that will be returned for this request,
- * or the frame number of the last frame that will be returned for the current repeating
- * request if this burst is set to be repeating.
- */
- public synchronized SubmitInfo submit(CaptureRequest[] requests, boolean repeating) {
- int requestId = mCurrentRequestId++;
- BurstHolder burst = new BurstHolder(requestId, repeating, requests, mJpegSurfaceIds);
- long lastFrame = INVALID_FRAME;
- if (burst.isRepeating()) {
- Log.i(TAG, "Repeating capture request set.");
- if (mRepeatingRequest != null) {
- lastFrame = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME :
- mCurrentRepeatingFrameNumber - 1;
- }
- mCurrentRepeatingFrameNumber = INVALID_FRAME;
- mRepeatingRequest = burst;
- } else {
- mRequestQueue.offer(burst);
- lastFrame = calculateLastFrame(burst.getRequestId());
- }
- SubmitInfo info = new SubmitInfo(requestId, lastFrame);
- return info;
- }
-
- private long calculateLastFrame(int requestId) {
- long total = mCurrentFrameNumber;
- for (BurstHolder b : mRequestQueue) {
- total += b.getNumberOfRequests();
- if (b.getRequestId() == requestId) {
- return total - 1;
- }
- }
- throw new IllegalStateException(
- "At least one request must be in the queue to calculate frame number");
- }
-
-}
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
deleted file mode 100644
index f9a5029..0000000
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ /dev/null
@@ -1,1126 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.graphics.SurfaceTexture;
-import android.hardware.Camera;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.impl.CameraDeviceImpl;
-import android.hardware.camera2.utils.SubmitInfo;
-import android.hardware.camera2.utils.SizeAreaComparator;
-import android.hardware.camera2.impl.CameraMetadataNative;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.util.Log;
-import android.util.MutableLong;
-import android.util.Pair;
-import android.util.Size;
-import android.view.Surface;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import static com.android.internal.util.Preconditions.*;
-
-/**
- * This class executes requests to the {@link Camera}.
- *
- * <p>
- * The main components of this class are:
- * - A message queue of requests to the {@link Camera}.
- * - A thread that consumes requests to the {@link Camera} and executes them.
- * - A {@link GLThreadManager} that draws to the configured output {@link Surface}s.
- * - An {@link CameraDeviceState} state machine that manages the callbacks for various operations.
- * </p>
- */
-@SuppressWarnings("deprecation")
-public class RequestThreadManager {
- private final String TAG;
- private final int mCameraId;
- private final RequestHandlerThread mRequestThread;
-
- private static final boolean DEBUG = false;
- // For slightly more spammy messages that will get repeated every frame
- private static final boolean VERBOSE = false;
- private Camera mCamera;
- private final CameraCharacteristics mCharacteristics;
-
- private final CameraDeviceState mDeviceState;
- private final CaptureCollector mCaptureCollector;
- private final LegacyFocusStateMapper mFocusStateMapper;
- private final LegacyFaceDetectMapper mFaceDetectMapper;
-
- private static final int MSG_CONFIGURE_OUTPUTS = 1;
- private static final int MSG_SUBMIT_CAPTURE_REQUEST = 2;
- private static final int MSG_CLEANUP = 3;
-
- private static final int MAX_IN_FLIGHT_REQUESTS = 2;
-
- private static final int PREVIEW_FRAME_TIMEOUT = 1000; // ms
- private static final int JPEG_FRAME_TIMEOUT = 4000; // ms (same as CTS for API2)
- private static final int REQUEST_COMPLETE_TIMEOUT = JPEG_FRAME_TIMEOUT;
-
- private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
- private boolean mPreviewRunning = false;
-
- private final List<Surface> mPreviewOutputs = new ArrayList<>();
- private final List<Surface> mCallbackOutputs = new ArrayList<>();
- private GLThreadManager mGLThreadManager;
- private SurfaceTexture mPreviewTexture;
- private Camera.Parameters mParams;
-
- private final List<Long> mJpegSurfaceIds = new ArrayList<>();
-
- private Size mIntermediateBufferSize;
-
- private final RequestQueue mRequestQueue = new RequestQueue(mJpegSurfaceIds);
- private LegacyRequest mLastRequest = null;
- private SurfaceTexture mDummyTexture;
- private Surface mDummySurface;
-
- private final Object mIdleLock = new Object();
- private final FpsCounter mPrevCounter = new FpsCounter("Incoming Preview");
- private final FpsCounter mRequestCounter = new FpsCounter("Incoming Requests");
-
- private final AtomicBoolean mQuit = new AtomicBoolean(false);
-
- // Stuff JPEGs into HAL_PIXEL_FORMAT_RGBA_8888 gralloc buffers to get around SW write
- // limitations for (b/17379185).
- private static final boolean USE_BLOB_FORMAT_OVERRIDE = true;
-
- /**
- * Container object for Configure messages.
- */
- private static class ConfigureHolder {
- public final ConditionVariable condition;
- public final Collection<Pair<Surface, Size>> surfaces;
-
- public ConfigureHolder(ConditionVariable condition, Collection<Pair<Surface,
- Size>> surfaces) {
- this.condition = condition;
- this.surfaces = surfaces;
- }
- }
-
- /**
- * Counter class used to calculate and log the current FPS of frame production.
- */
- public static class FpsCounter {
- //TODO: Hook this up to SystTrace?
- private static final String TAG = "FpsCounter";
- private int mFrameCount = 0;
- private long mLastTime = 0;
- private long mLastPrintTime = 0;
- private double mLastFps = 0;
- private final String mStreamType;
- private static final long NANO_PER_SECOND = 1000000000; //ns
-
- public FpsCounter(String streamType) {
- mStreamType = streamType;
- }
-
- public synchronized void countFrame() {
- mFrameCount++;
- long nextTime = SystemClock.elapsedRealtimeNanos();
- if (mLastTime == 0) {
- mLastTime = nextTime;
- }
- if (nextTime > mLastTime + NANO_PER_SECOND) {
- long elapsed = nextTime - mLastTime;
- mLastFps = mFrameCount * (NANO_PER_SECOND / (double) elapsed);
- mFrameCount = 0;
- mLastTime = nextTime;
- }
- }
-
- public synchronized double checkFps() {
- return mLastFps;
- }
-
- public synchronized void staggeredLog() {
- if (mLastTime > mLastPrintTime + 5 * NANO_PER_SECOND) {
- mLastPrintTime = mLastTime;
- Log.d(TAG, "FPS for " + mStreamType + " stream: " + mLastFps );
- }
- }
-
- public synchronized void countAndLog() {
- countFrame();
- staggeredLog();
- }
- }
- /**
- * Fake preview for jpeg captures when there is no active preview
- */
- private void createDummySurface() {
- if (mDummyTexture == null || mDummySurface == null) {
- mDummyTexture = new SurfaceTexture(/*ignored*/0);
- // TODO: use smallest default sizes
- mDummyTexture.setDefaultBufferSize(640, 480);
- mDummySurface = new Surface(mDummyTexture);
- }
- }
-
- private final Camera.ErrorCallback mErrorCallback = new Camera.ErrorCallback() {
- @Override
- public void onError(int i, Camera camera) {
- switch(i) {
- case Camera.CAMERA_ERROR_EVICTED: {
- flush();
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DISCONNECTED);
- } break;
- case Camera.CAMERA_ERROR_DISABLED: {
- flush();
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DISABLED);
- } break;
- default: {
- Log.e(TAG, "Received error " + i + " from the Camera1 ErrorCallback");
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
- } break;
- }
- }
- };
-
- private final ConditionVariable mReceivedJpeg = new ConditionVariable(false);
-
- private final Camera.PictureCallback mJpegCallback = new Camera.PictureCallback() {
- @Override
- public void onPictureTaken(byte[] data, Camera camera) {
- Log.i(TAG, "Received jpeg.");
- Pair<RequestHolder, Long> captureInfo = mCaptureCollector.jpegProduced();
- if (captureInfo == null || captureInfo.first == null) {
- Log.e(TAG, "Dropping jpeg frame.");
- return;
- }
- RequestHolder holder = captureInfo.first;
- long timestamp = captureInfo.second;
- for (Surface s : holder.getHolderTargets()) {
- try {
- if (LegacyCameraDevice.containsSurfaceId(s, mJpegSurfaceIds)) {
- Log.i(TAG, "Producing jpeg buffer...");
-
- int totalSize = data.length + LegacyCameraDevice.nativeGetJpegFooterSize();
- totalSize = (totalSize + 3) & ~0x3; // round up to nearest octonibble
- LegacyCameraDevice.setNextTimestamp(s, timestamp);
-
- if (USE_BLOB_FORMAT_OVERRIDE) {
- // Override to RGBA_8888 format.
- LegacyCameraDevice.setSurfaceFormat(s,
- LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888);
-
- int dimen = (int) Math.ceil(Math.sqrt(totalSize));
- dimen = (dimen + 0xf) & ~0xf; // round up to nearest multiple of 16
- LegacyCameraDevice.setSurfaceDimens(s, dimen, dimen);
- LegacyCameraDevice.produceFrame(s, data, dimen, dimen,
- CameraMetadataNative.NATIVE_JPEG_FORMAT);
- } else {
- LegacyCameraDevice.setSurfaceDimens(s, totalSize, /*height*/1);
- LegacyCameraDevice.produceFrame(s, data, totalSize, /*height*/1,
- CameraMetadataNative.NATIVE_JPEG_FORMAT);
- }
- }
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.w(TAG, "Surface abandoned, dropping frame. ", e);
- }
- }
-
- mReceivedJpeg.open();
- }
- };
-
- private final Camera.ShutterCallback mJpegShutterCallback = new Camera.ShutterCallback() {
- @Override
- public void onShutter() {
- mCaptureCollector.jpegCaptured(SystemClock.elapsedRealtimeNanos());
- }
- };
-
- private final SurfaceTexture.OnFrameAvailableListener mPreviewCallback =
- new SurfaceTexture.OnFrameAvailableListener() {
- @Override
- public void onFrameAvailable(SurfaceTexture surfaceTexture) {
- if (DEBUG) {
- mPrevCounter.countAndLog();
- }
- mGLThreadManager.queueNewFrame();
- }
- };
-
- private void stopPreview() {
- if (VERBOSE) {
- Log.v(TAG, "stopPreview - preview running? " + mPreviewRunning);
- }
- if (mPreviewRunning) {
- mCamera.stopPreview();
- mPreviewRunning = false;
- }
- }
-
- private void startPreview() {
- if (VERBOSE) {
- Log.v(TAG, "startPreview - preview running? " + mPreviewRunning);
- }
- if (!mPreviewRunning) {
- // XX: CameraClient:;startPreview is not getting called after a stop
- mCamera.startPreview();
- mPreviewRunning = true;
- }
- }
-
- private void doJpegCapturePrepare(RequestHolder request) throws IOException {
- if (DEBUG) Log.d(TAG, "doJpegCapturePrepare - preview running? " + mPreviewRunning);
-
- if (!mPreviewRunning) {
- if (DEBUG) Log.d(TAG, "doJpegCapture - create fake surface");
-
- createDummySurface();
- mCamera.setPreviewTexture(mDummyTexture);
- startPreview();
- }
- }
-
- private void doJpegCapture(RequestHolder request) {
- if (DEBUG) Log.d(TAG, "doJpegCapturePrepare");
-
- mCamera.takePicture(mJpegShutterCallback, /*raw*/null, mJpegCallback);
- mPreviewRunning = false;
- }
-
- private void doPreviewCapture(RequestHolder request) throws IOException {
- if (VERBOSE) {
- Log.v(TAG, "doPreviewCapture - preview running? " + mPreviewRunning);
- }
-
- if (mPreviewRunning) {
- return; // Already running
- }
-
- if (mPreviewTexture == null) {
- throw new IllegalStateException(
- "Preview capture called with no preview surfaces configured.");
- }
-
- mPreviewTexture.setDefaultBufferSize(mIntermediateBufferSize.getWidth(),
- mIntermediateBufferSize.getHeight());
- mCamera.setPreviewTexture(mPreviewTexture);
-
- startPreview();
- }
-
- private void disconnectCallbackSurfaces() {
- for (Surface s : mCallbackOutputs) {
- try {
- LegacyCameraDevice.disconnectSurface(s);
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.d(TAG, "Surface abandoned, skipping...", e);
- }
- }
- }
-
- private void configureOutputs(Collection<Pair<Surface, Size>> outputs) {
- if (DEBUG) {
- String outputsStr = outputs == null ? "null" : (outputs.size() + " surfaces");
- Log.d(TAG, "configureOutputs with " + outputsStr);
- }
-
- try {
- stopPreview();
- } catch (RuntimeException e) {
- Log.e(TAG, "Received device exception in configure call: ", e);
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
- return;
- }
-
- /*
- * Try to release the previous preview's surface texture earlier if we end up
- * using a different one; this also reduces the likelihood of getting into a deadlock
- * when disconnecting from the old previous texture at a later time.
- */
- try {
- mCamera.setPreviewTexture(/*surfaceTexture*/null);
- } catch (IOException e) {
- Log.w(TAG, "Failed to clear prior SurfaceTexture, may cause GL deadlock: ", e);
- } catch (RuntimeException e) {
- Log.e(TAG, "Received device exception in configure call: ", e);
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
- return;
- }
-
- if (mGLThreadManager != null) {
- mGLThreadManager.waitUntilStarted();
- mGLThreadManager.ignoreNewFrames();
- mGLThreadManager.waitUntilIdle();
- }
- resetJpegSurfaceFormats(mCallbackOutputs);
- disconnectCallbackSurfaces();
-
- mPreviewOutputs.clear();
- mCallbackOutputs.clear();
- mJpegSurfaceIds.clear();
- mPreviewTexture = null;
-
- List<Size> previewOutputSizes = new ArrayList<>();
- List<Size> callbackOutputSizes = new ArrayList<>();
-
- int facing = mCharacteristics.get(CameraCharacteristics.LENS_FACING);
- int orientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
- if (outputs != null) {
- for (Pair<Surface, Size> outPair : outputs) {
- Surface s = outPair.first;
- Size outSize = outPair.second;
- try {
- int format = LegacyCameraDevice.detectSurfaceType(s);
- LegacyCameraDevice.setSurfaceOrientation(s, facing, orientation);
- switch (format) {
- case CameraMetadataNative.NATIVE_JPEG_FORMAT:
- if (USE_BLOB_FORMAT_OVERRIDE) {
- // Override to RGBA_8888 format.
- LegacyCameraDevice.setSurfaceFormat(s,
- LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888);
- }
- mJpegSurfaceIds.add(LegacyCameraDevice.getSurfaceId(s));
- mCallbackOutputs.add(s);
- callbackOutputSizes.add(outSize);
-
- // LegacyCameraDevice is the producer of JPEG output surfaces
- // so LegacyCameraDevice needs to connect to the surfaces.
- LegacyCameraDevice.connectSurface(s);
- break;
- default:
- LegacyCameraDevice.setScalingMode(s, LegacyCameraDevice.
- NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
- mPreviewOutputs.add(s);
- previewOutputSizes.add(outSize);
- break;
- }
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.w(TAG, "Surface abandoned, skipping...", e);
- }
- }
- }
- try {
- mParams = mCamera.getParameters();
- } catch (RuntimeException e) {
- Log.e(TAG, "Received device exception: ", e);
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
- return;
- }
-
- List<int[]> supportedFpsRanges = mParams.getSupportedPreviewFpsRange();
- int[] bestRange = getPhotoPreviewFpsRange(supportedFpsRanges);
- if (DEBUG) {
- Log.d(TAG, "doPreviewCapture - Selected range [" +
- bestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] + "," +
- bestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] + "]");
- }
- mParams.setPreviewFpsRange(bestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
- bestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
-
- Size smallestSupportedJpegSize = calculatePictureSize(mCallbackOutputs,
- callbackOutputSizes, mParams);
-
- if (previewOutputSizes.size() > 0) {
-
- Size largestOutput = SizeAreaComparator.findLargestByArea(previewOutputSizes);
-
- // Find largest jpeg dimension - assume to have the same aspect ratio as sensor.
- Size largestJpegDimen = ParameterUtils.getLargestSupportedJpegSizeByArea(mParams);
-
- Size chosenJpegDimen = (smallestSupportedJpegSize != null) ? smallestSupportedJpegSize
- : largestJpegDimen;
-
- List<Size> supportedPreviewSizes = ParameterUtils.convertSizeList(
- mParams.getSupportedPreviewSizes());
-
- // Use smallest preview dimension with same aspect ratio as sensor that is >= than all
- // of the configured output dimensions. If none exists, fall back to using the largest
- // supported preview size.
- long largestOutputArea = largestOutput.getHeight() * (long) largestOutput.getWidth();
- Size bestPreviewDimen = SizeAreaComparator.findLargestByArea(supportedPreviewSizes);
- for (Size s : supportedPreviewSizes) {
- long currArea = s.getWidth() * s.getHeight();
- long bestArea = bestPreviewDimen.getWidth() * bestPreviewDimen.getHeight();
- if (checkAspectRatiosMatch(chosenJpegDimen, s) && (currArea < bestArea &&
- currArea >= largestOutputArea)) {
- bestPreviewDimen = s;
- }
- }
-
- mIntermediateBufferSize = bestPreviewDimen;
- mParams.setPreviewSize(mIntermediateBufferSize.getWidth(),
- mIntermediateBufferSize.getHeight());
-
- if (DEBUG) {
- Log.d(TAG, "Intermediate buffer selected with dimens: " +
- bestPreviewDimen.toString());
- }
- } else {
- mIntermediateBufferSize = null;
- if (DEBUG) {
- Log.d(TAG, "No Intermediate buffer selected, no preview outputs were configured");
- }
- }
-
- if (smallestSupportedJpegSize != null) {
- /*
- * Set takePicture size to the smallest supported JPEG size large enough
- * to scale/crop out of for the bounding rectangle of the configured JPEG sizes.
- */
-
- Log.i(TAG, "configureOutputs - set take picture size to " + smallestSupportedJpegSize);
- mParams.setPictureSize(
- smallestSupportedJpegSize.getWidth(), smallestSupportedJpegSize.getHeight());
- }
-
- // TODO: Detect and optimize single-output paths here to skip stream teeing.
- if (mGLThreadManager == null) {
- mGLThreadManager = new GLThreadManager(mCameraId, facing, mDeviceState);
- mGLThreadManager.start();
- }
- mGLThreadManager.waitUntilStarted();
- List<Pair<Surface, Size>> previews = new ArrayList<>();
- Iterator<Size> previewSizeIter = previewOutputSizes.iterator();
- for (Surface p : mPreviewOutputs) {
- previews.add(new Pair<>(p, previewSizeIter.next()));
- }
- mGLThreadManager.setConfigurationAndWait(previews, mCaptureCollector);
-
- for (Surface p : mPreviewOutputs) {
- try {
- LegacyCameraDevice.setSurfaceOrientation(p, facing, orientation);
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.e(TAG, "Surface abandoned, skipping setSurfaceOrientation()", e);
- }
- }
-
- mGLThreadManager.allowNewFrames();
- mPreviewTexture = mGLThreadManager.getCurrentSurfaceTexture();
- if (mPreviewTexture != null) {
- mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback);
- }
-
- try {
- mCamera.setParameters(mParams);
- } catch (RuntimeException e) {
- Log.e(TAG, "Received device exception while configuring: ", e);
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
-
- }
- }
-
- private void resetJpegSurfaceFormats(Collection<Surface> surfaces) {
- if (!USE_BLOB_FORMAT_OVERRIDE || surfaces == null) {
- return;
- }
- for(Surface s : surfaces) {
- if (s == null || !s.isValid()) {
- Log.w(TAG, "Jpeg surface is invalid, skipping...");
- continue;
- }
- try {
- LegacyCameraDevice.setSurfaceFormat(s, LegacyMetadataMapper.HAL_PIXEL_FORMAT_BLOB);
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.w(TAG, "Surface abandoned, skipping...", e);
- }
- }
- }
-
- /**
- * Find a JPEG size (that is supported by the legacy camera device) which is equal to or larger
- * than all of the configured {@code JPEG} outputs (by both width and height).
- *
- * <p>If multiple supported JPEG sizes are larger, select the smallest of them which
- * still satisfies the above constraint.</p>
- *
- * <p>As a result, the returned size is guaranteed to be usable without needing
- * to upscale any of the outputs. If only one {@code JPEG} surface is used,
- * then no scaling/cropping is necessary between the taken picture and
- * the {@code JPEG} output surface.</p>
- *
- * @param callbackOutputs a non-{@code null} list of {@code Surface}s with any image formats
- * @param params api1 parameters (used for reading only)
- *
- * @return a size large enough to fit all of the configured {@code JPEG} outputs, or
- * {@code null} if the {@code callbackOutputs} did not have any {@code JPEG}
- * surfaces.
- */
- private Size calculatePictureSize( List<Surface> callbackOutputs,
- List<Size> callbackSizes, Camera.Parameters params) {
- /*
- * Find the largest JPEG size (if any), from the configured outputs:
- * - the api1 picture size should be set to the smallest legal size that's at least as large
- * as the largest configured JPEG size
- */
- if (callbackOutputs.size() != callbackSizes.size()) {
- throw new IllegalStateException("Input collections must be same length");
- }
- List<Size> configuredJpegSizes = new ArrayList<>();
- Iterator<Size> sizeIterator = callbackSizes.iterator();
- for (Surface callbackSurface : callbackOutputs) {
- Size jpegSize = sizeIterator.next();
- if (!LegacyCameraDevice.containsSurfaceId(callbackSurface, mJpegSurfaceIds)) {
- continue; // Ignore non-JPEG callback formats
- }
-
- configuredJpegSizes.add(jpegSize);
- }
- if (!configuredJpegSizes.isEmpty()) {
- /*
- * Find the largest configured JPEG width, and height, independently
- * of the rest.
- *
- * The rest of the JPEG streams can be cropped out of this smallest bounding
- * rectangle.
- */
- int maxConfiguredJpegWidth = -1;
- int maxConfiguredJpegHeight = -1;
- for (Size jpegSize : configuredJpegSizes) {
- maxConfiguredJpegWidth = jpegSize.getWidth() > maxConfiguredJpegWidth ?
- jpegSize.getWidth() : maxConfiguredJpegWidth;
- maxConfiguredJpegHeight = jpegSize.getHeight() > maxConfiguredJpegHeight ?
- jpegSize.getHeight() : maxConfiguredJpegHeight;
- }
- Size smallestBoundJpegSize = new Size(maxConfiguredJpegWidth, maxConfiguredJpegHeight);
-
- List<Size> supportedJpegSizes = ParameterUtils.convertSizeList(
- params.getSupportedPictureSizes());
-
- /*
- * Find the smallest supported JPEG size that can fit the smallest bounding
- * rectangle for the configured JPEG sizes.
- */
- List<Size> candidateSupportedJpegSizes = new ArrayList<>();
- for (Size supportedJpegSize : supportedJpegSizes) {
- if (supportedJpegSize.getWidth() >= maxConfiguredJpegWidth &&
- supportedJpegSize.getHeight() >= maxConfiguredJpegHeight) {
- candidateSupportedJpegSizes.add(supportedJpegSize);
- }
- }
-
- if (candidateSupportedJpegSizes.isEmpty()) {
- throw new AssertionError(
- "Could not find any supported JPEG sizes large enough to fit " +
- smallestBoundJpegSize);
- }
-
- Size smallestSupportedJpegSize = Collections.min(candidateSupportedJpegSizes,
- new SizeAreaComparator());
-
- if (!smallestSupportedJpegSize.equals(smallestBoundJpegSize)) {
- Log.w(TAG,
- String.format(
- "configureOutputs - Will need to crop picture %s into "
- + "smallest bound size %s",
- smallestSupportedJpegSize, smallestBoundJpegSize));
- }
-
- return smallestSupportedJpegSize;
- }
-
- return null;
- }
-
- private static boolean checkAspectRatiosMatch(Size a, Size b) {
- float aAspect = a.getWidth() / (float) a.getHeight();
- float bAspect = b.getWidth() / (float) b.getHeight();
-
- return Math.abs(aAspect - bAspect) < ASPECT_RATIO_TOLERANCE;
- }
-
- // Calculate the highest FPS range supported
- private int[] getPhotoPreviewFpsRange(List<int[]> frameRates) {
- if (frameRates.size() == 0) {
- Log.e(TAG, "No supported frame rates returned!");
- return null;
- }
-
- int bestMin = 0;
- int bestMax = 0;
- int bestIndex = 0;
- int index = 0;
- for (int[] rate : frameRates) {
- int minFps = rate[Camera.Parameters.PREVIEW_FPS_MIN_INDEX];
- int maxFps = rate[Camera.Parameters.PREVIEW_FPS_MAX_INDEX];
- if (maxFps > bestMax || (maxFps == bestMax && minFps > bestMin)) {
- bestMin = minFps;
- bestMax = maxFps;
- bestIndex = index;
- }
- index++;
- }
-
- return frameRates.get(bestIndex);
- }
-
- private final Handler.Callback mRequestHandlerCb = new Handler.Callback() {
- private boolean mCleanup = false;
- private final LegacyResultMapper mMapper = new LegacyResultMapper();
-
- @Override
- public boolean handleMessage(Message msg) {
- if (mCleanup) {
- return true;
- }
-
- if (DEBUG) {
- Log.d(TAG, "Request thread handling message:" + msg.what);
- }
- long startTime = 0;
- if (DEBUG) {
- startTime = SystemClock.elapsedRealtimeNanos();
- }
- switch (msg.what) {
- case MSG_CONFIGURE_OUTPUTS:
- ConfigureHolder config = (ConfigureHolder) msg.obj;
- int sizes = config.surfaces != null ? config.surfaces.size() : 0;
- Log.i(TAG, "Configure outputs: " + sizes + " surfaces configured.");
-
- try {
- boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT,
- TimeUnit.MILLISECONDS);
- if (!success) {
- Log.e(TAG, "Timed out while queueing configure request.");
- mCaptureCollector.failAll();
- }
- } catch (InterruptedException e) {
- Log.e(TAG, "Interrupted while waiting for requests to complete.");
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
- break;
- }
-
- configureOutputs(config.surfaces);
- config.condition.open();
- if (DEBUG) {
- long totalTime = SystemClock.elapsedRealtimeNanos() - startTime;
- Log.d(TAG, "Configure took " + totalTime + " ns");
- }
- break;
- case MSG_SUBMIT_CAPTURE_REQUEST:
- Handler handler = RequestThreadManager.this.mRequestThread.getHandler();
- boolean anyRequestOutputAbandoned = false;
-
- // Get the next burst from the request queue.
- RequestQueue.RequestQueueEntry nextBurst = mRequestQueue.getNext();
-
- if (nextBurst == null) {
- // If there are no further requests queued, wait for any currently executing
- // requests to complete, then switch to idle state.
- try {
- boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT,
- TimeUnit.MILLISECONDS);
- if (!success) {
- Log.e(TAG,
- "Timed out while waiting for prior requests to complete.");
- mCaptureCollector.failAll();
- }
- } catch (InterruptedException e) {
- Log.e(TAG, "Interrupted while waiting for requests to complete: ", e);
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
- break;
- }
-
- synchronized (mIdleLock) {
- // Retry the the request queue.
- nextBurst = mRequestQueue.getNext();
-
- // If we still have no queued requests, go idle.
- if (nextBurst == null) {
- mDeviceState.setIdle();
- break;
- }
- }
- }
-
- if (nextBurst != null) {
- // Queue another capture if we did not get the last burst.
- handler.sendEmptyMessage(MSG_SUBMIT_CAPTURE_REQUEST);
-
- // Check whether capture queue becomes empty
- if (nextBurst.isQueueEmpty()) {
- mDeviceState.setRequestQueueEmpty();
- }
- }
-
- // Complete each request in the burst
- BurstHolder burstHolder = nextBurst.getBurstHolder();
- List<RequestHolder> requests =
- burstHolder.produceRequestHolders(nextBurst.getFrameNumber());
- for (RequestHolder holder : requests) {
- CaptureRequest request = holder.getRequest();
-
- boolean paramsChanged = false;
-
- // Only update parameters if the request has changed
- if (mLastRequest == null || mLastRequest.captureRequest != request) {
-
- // The intermediate buffer is sometimes null, but we always need
- // the Camera1 API configured preview size
- Size previewSize = ParameterUtils.convertSize(mParams.getPreviewSize());
-
- LegacyRequest legacyRequest = new LegacyRequest(mCharacteristics,
- request, previewSize, mParams); // params are copied
-
-
- // Parameters are mutated as a side-effect
- LegacyMetadataMapper.convertRequestMetadata(/*inout*/legacyRequest);
-
- // If the parameters have changed, set them in the Camera1 API.
- if (!mParams.same(legacyRequest.parameters)) {
- try {
- mCamera.setParameters(legacyRequest.parameters);
- } catch (RuntimeException e) {
- // If setting the parameters failed, report a request error to
- // the camera client, and skip any further work for this request
- Log.e(TAG, "Exception while setting camera parameters: ", e);
- holder.failRequest();
- mDeviceState.setCaptureStart(holder, /*timestamp*/0,
- CameraDeviceImpl.CameraDeviceCallbacks.
- ERROR_CAMERA_REQUEST);
- continue;
- }
- paramsChanged = true;
- mParams = legacyRequest.parameters;
- }
-
- mLastRequest = legacyRequest;
- }
-
- try {
- boolean success = mCaptureCollector.queueRequest(holder,
- mLastRequest, JPEG_FRAME_TIMEOUT, TimeUnit.MILLISECONDS);
-
- if (!success) {
- // Report a request error if we timed out while queuing this.
- Log.e(TAG, "Timed out while queueing capture request.");
- holder.failRequest();
- mDeviceState.setCaptureStart(holder, /*timestamp*/0,
- CameraDeviceImpl.CameraDeviceCallbacks.
- ERROR_CAMERA_REQUEST);
- continue;
- }
-
- // Starting the preview needs to happen before enabling
- // face detection or auto focus
- if (holder.hasPreviewTargets()) {
- doPreviewCapture(holder);
- }
- if (holder.hasJpegTargets()) {
- while(!mCaptureCollector.waitForPreviewsEmpty(PREVIEW_FRAME_TIMEOUT,
- TimeUnit.MILLISECONDS)) {
- // Fail preview requests until the queue is empty.
- Log.e(TAG, "Timed out while waiting for preview requests to " +
- "complete.");
- mCaptureCollector.failNextPreview();
- }
- mReceivedJpeg.close();
- doJpegCapturePrepare(holder);
- }
-
- /*
- * Do all the actions that require a preview to have been started
- */
-
- // Toggle face detection on/off
- // - do this before AF to give AF a chance to use faces
- mFaceDetectMapper.processFaceDetectMode(request, /*in*/mParams);
-
- // Unconditionally process AF triggers, since they're non-idempotent
- // - must be done after setting the most-up-to-date AF mode
- mFocusStateMapper.processRequestTriggers(request, mParams);
-
- if (holder.hasJpegTargets()) {
- doJpegCapture(holder);
- if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) {
- Log.e(TAG, "Hit timeout for jpeg callback!");
- mCaptureCollector.failNextJpeg();
- }
- }
-
- } catch (IOException e) {
- Log.e(TAG, "Received device exception during capture call: ", e);
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
- break;
- } catch (InterruptedException e) {
- Log.e(TAG, "Interrupted during capture: ", e);
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
- break;
- } catch (RuntimeException e) {
- Log.e(TAG, "Received device exception during capture call: ", e);
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
- break;
- }
-
- if (paramsChanged) {
- if (DEBUG) {
- Log.d(TAG, "Params changed -- getting new Parameters from HAL.");
- }
- try {
- mParams = mCamera.getParameters();
- } catch (RuntimeException e) {
- Log.e(TAG, "Received device exception: ", e);
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
- break;
- }
-
- // Update parameters to the latest that we think the camera is using
- mLastRequest.setParameters(mParams);
- }
-
- MutableLong timestampMutable = new MutableLong(/*value*/0L);
- try {
- boolean success = mCaptureCollector.waitForRequestCompleted(holder,
- REQUEST_COMPLETE_TIMEOUT, TimeUnit.MILLISECONDS,
- /*out*/timestampMutable);
-
- if (!success) {
- Log.e(TAG, "Timed out while waiting for request to complete.");
- mCaptureCollector.failAll();
- }
- } catch (InterruptedException e) {
- Log.e(TAG, "Interrupted waiting for request completion: ", e);
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
- break;
- }
-
- CameraMetadataNative result = mMapper.cachedConvertResultMetadata(
- mLastRequest, timestampMutable.value);
- /*
- * Order matters: The default result mapper is state-less; the
- * other mappers carry state and may override keys set by the default
- * mapper with their own values.
- */
-
- // Update AF state
- mFocusStateMapper.mapResultTriggers(result);
- // Update face-related results
- mFaceDetectMapper.mapResultFaces(result, mLastRequest);
-
- if (!holder.requestFailed()) {
- mDeviceState.setCaptureResult(holder, result);
- }
-
- if (holder.isOutputAbandoned()) {
- anyRequestOutputAbandoned = true;
- }
- }
-
- // Stop the repeating request if any of its output surfaces is abandoned.
- if (anyRequestOutputAbandoned && burstHolder.isRepeating()) {
- long lastFrameNumber = cancelRepeating(burstHolder.getRequestId());
- if (DEBUG) {
- Log.d(TAG, "Stopped repeating request. Last frame number is " +
- lastFrameNumber);
- }
- if (lastFrameNumber != RequestQueue.INVALID_FRAME) {
- mDeviceState.setRepeatingRequestError(lastFrameNumber,
- burstHolder.getRequestId());
- } else {
- Log.e(TAG, "Repeating request id: " + burstHolder.getRequestId() +
- " already canceled!");
- }
- }
-
- if (DEBUG) {
- long totalTime = SystemClock.elapsedRealtimeNanos() - startTime;
- Log.d(TAG, "Capture request took " + totalTime + " ns");
- mRequestCounter.countAndLog();
- }
- break;
- case MSG_CLEANUP:
- mCleanup = true;
- try {
- boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT,
- TimeUnit.MILLISECONDS);
- if (!success) {
- Log.e(TAG, "Timed out while queueing cleanup request.");
- mCaptureCollector.failAll();
- }
- } catch (InterruptedException e) {
- Log.e(TAG, "Interrupted while waiting for requests to complete: ", e);
- mDeviceState.setError(
- CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
- }
- if (mGLThreadManager != null) {
- mGLThreadManager.quit();
- mGLThreadManager = null;
- }
- disconnectCallbackSurfaces();
- if (mCamera != null) {
- mCamera.release();
- mCamera = null;
- }
- break;
- case RequestHandlerThread.MSG_POKE_IDLE_HANDLER:
- // OK: Ignore message.
- break;
- default:
- throw new AssertionError("Unhandled message " + msg.what +
- " on RequestThread.");
- }
- return true;
- }
- };
-
- /**
- * Create a new RequestThreadManager.
- *
- * @param cameraId the id of the camera to use.
- * @param camera an open camera object. The RequestThreadManager takes ownership of this camera
- * object, and is responsible for closing it.
- * @param characteristics the static camera characteristics corresponding to this camera device
- * @param deviceState a {@link CameraDeviceState} state machine.
- */
- public RequestThreadManager(int cameraId, Camera camera, CameraCharacteristics characteristics,
- CameraDeviceState deviceState) {
- mCamera = checkNotNull(camera, "camera must not be null");
- mCameraId = cameraId;
- mCharacteristics = checkNotNull(characteristics, "characteristics must not be null");
- String name = String.format("RequestThread-%d", cameraId);
- TAG = name;
- mDeviceState = checkNotNull(deviceState, "deviceState must not be null");
- mFocusStateMapper = new LegacyFocusStateMapper(mCamera);
- mFaceDetectMapper = new LegacyFaceDetectMapper(mCamera, mCharacteristics);
- mCaptureCollector = new CaptureCollector(MAX_IN_FLIGHT_REQUESTS, mDeviceState);
- mRequestThread = new RequestHandlerThread(name, mRequestHandlerCb);
- mCamera.setDetailedErrorCallback(mErrorCallback);
- }
-
- /**
- * Start the request thread.
- */
- public void start() {
- mRequestThread.start();
- }
-
- /**
- * Flush any pending requests.
- *
- * @return the last frame number.
- */
- public long flush() {
- Log.i(TAG, "Flushing all pending requests.");
- long lastFrame = mRequestQueue.stopRepeating();
- mCaptureCollector.failAll();
- return lastFrame;
- }
-
- /**
- * Quit the request thread, and clean up everything.
- */
- public void quit() {
- if (!mQuit.getAndSet(true)) { // Avoid sending messages on dead thread's handler.
- Handler handler = mRequestThread.waitAndGetHandler();
- handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
- mRequestThread.quitSafely();
- try {
- mRequestThread.join();
- } catch (InterruptedException e) {
- Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
- mRequestThread.getName(), mRequestThread.getId()));
- }
- }
- }
-
- /**
- * Submit the given burst of requests to be captured.
- *
- * <p>If the burst is repeating, replace the current repeating burst.</p>
- *
- * @param requests the burst of requests to add to the queue.
- * @param repeating true if the burst is repeating.
- * @return the submission info, including the new request id, and the last frame number, which
- * contains either the frame number of the last frame that will be returned for this request,
- * or the frame number of the last frame that will be returned for the current repeating
- * request if this burst is set to be repeating.
- */
- public SubmitInfo submitCaptureRequests(CaptureRequest[] requests, boolean repeating) {
- Handler handler = mRequestThread.waitAndGetHandler();
- SubmitInfo info;
- synchronized (mIdleLock) {
- info = mRequestQueue.submit(requests, repeating);
- handler.sendEmptyMessage(MSG_SUBMIT_CAPTURE_REQUEST);
- }
- return info;
- }
-
- /**
- * Cancel a repeating request.
- *
- * @param requestId the id of the repeating request to cancel.
- * @return the last frame to be returned from the HAL for the given repeating request, or
- * {@code INVALID_FRAME} if none exists.
- */
- public long cancelRepeating(int requestId) {
- return mRequestQueue.stopRepeating(requestId);
- }
-
- /**
- * Configure with the current list of output Surfaces.
- *
- * <p>
- * This operation blocks until the configuration is complete.
- * </p>
- *
- * <p>Using a {@code null} or empty {@code outputs} list is the equivalent of unconfiguring.</p>
- *
- * @param outputs a {@link java.util.Collection} of outputs to configure.
- */
- public void configure(Collection<Pair<Surface, Size>> outputs) {
- Handler handler = mRequestThread.waitAndGetHandler();
- final ConditionVariable condition = new ConditionVariable(/*closed*/false);
- ConfigureHolder holder = new ConfigureHolder(condition, outputs);
- handler.sendMessage(handler.obtainMessage(MSG_CONFIGURE_OUTPUTS, 0, 0, holder));
- condition.block();
- }
-
- public void setAudioRestriction(int mode) {
- if (mCamera != null) {
- mCamera.setAudioRestriction(mode);
- }
- throw new IllegalStateException("Camera has been released!");
- }
-
- public int getAudioRestriction() {
- if (mCamera != null) {
- return mCamera.getAudioRestriction();
- }
- throw new IllegalStateException("Camera has been released!");
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/SizeAreaComparator.java b/core/java/android/hardware/camera2/legacy/SizeAreaComparator.java
deleted file mode 100644
index 75a5bab..0000000
--- a/core/java/android/hardware/camera2/legacy/SizeAreaComparator.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.hardware.Camera;
-
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-import static com.android.internal.util.Preconditions.*;
-
-/**
- * Comparator for api1 {@link Camera.Size} objects by the area.
- *
- * <p>This comparator totally orders by rectangle area. Tie-breaks on width.</p>
- */
-@SuppressWarnings("deprecation")
-public class SizeAreaComparator implements Comparator<Camera.Size> {
- /**
- * {@inheritDoc}
- */
- @Override
- public int compare(Camera.Size size, Camera.Size size2) {
- checkNotNull(size, "size must not be null");
- checkNotNull(size2, "size2 must not be null");
-
- if (size.equals(size2)) {
- return 0;
- }
-
- long width = size.width;
- long width2 = size2.width;
- long area = width * size.height;
- long area2 = width2 * size2.height;
-
- if (area == area2) {
- return (width > width2) ? 1 : -1;
- }
-
- return (area > area2) ? 1 : -1;
- }
-
- /**
- * Get the largest api1 {@code Camera.Size} from the list by comparing each size's area
- * by each other using {@link SizeAreaComparator}.
- *
- * @param sizes a non-{@code null} list of non-{@code null} sizes
- * @return a non-{@code null} size
- *
- * @throws NullPointerException if {@code sizes} or any elements in it were {@code null}
- */
- public static Camera.Size findLargestByArea(List<Camera.Size> sizes) {
- checkNotNull(sizes, "sizes must not be null");
-
- return Collections.max(sizes, new SizeAreaComparator());
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
deleted file mode 100644
index a4c65ae..0000000
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ /dev/null
@@ -1,882 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.camera2.legacy;
-
-import android.graphics.ImageFormat;
-import android.graphics.RectF;
-import android.graphics.SurfaceTexture;
-import android.hardware.camera2.CameraCharacteristics;
-import android.os.Environment;
-import android.opengl.EGL14;
-import android.opengl.EGLConfig;
-import android.opengl.EGLContext;
-import android.opengl.EGLDisplay;
-import android.opengl.EGLSurface;
-import android.opengl.GLES11Ext;
-import android.opengl.GLES20;
-import android.opengl.Matrix;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Size;
-import android.view.Surface;
-import android.os.SystemProperties;
-
-import java.io.File;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * A renderer class that manages the GL state, and can draw a frame into a set of output
- * {@link Surface}s.
- */
-public class SurfaceTextureRenderer {
- private static final String TAG = SurfaceTextureRenderer.class.getSimpleName();
- private static final boolean DEBUG = false;
- private static final int EGL_RECORDABLE_ANDROID = 0x3142; // from EGL/eglext.h
- private static final int GL_MATRIX_SIZE = 16;
- private static final int VERTEX_POS_SIZE = 3;
- private static final int VERTEX_UV_SIZE = 2;
- private static final int EGL_COLOR_BITLENGTH = 8;
- private static final int GLES_VERSION = 2;
- private static final int PBUFFER_PIXEL_BYTES = 4;
-
- private static final int FLIP_TYPE_NONE = 0;
- private static final int FLIP_TYPE_HORIZONTAL = 1;
- private static final int FLIP_TYPE_VERTICAL = 2;
- private static final int FLIP_TYPE_BOTH = FLIP_TYPE_HORIZONTAL | FLIP_TYPE_VERTICAL;
-
- private static final DateTimeFormatter LOG_NAME_TIME_FORMATTER =
- DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss", Locale.ROOT);
-
- private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
- private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
- private EGLConfig mConfigs;
-
- private class EGLSurfaceHolder {
- Surface surface;
- EGLSurface eglSurface;
- int width;
- int height;
- }
-
- private List<EGLSurfaceHolder> mSurfaces = new ArrayList<EGLSurfaceHolder>();
- private List<EGLSurfaceHolder> mConversionSurfaces = new ArrayList<EGLSurfaceHolder>();
-
- private ByteBuffer mPBufferPixels;
-
- // Hold this to avoid GC
- private volatile SurfaceTexture mSurfaceTexture;
-
- private static final int FLOAT_SIZE_BYTES = 4;
- private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
- private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
- private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
-
- // Sampling is mirrored across the horizontal axis
- private static final float[] sHorizontalFlipTriangleVertices = {
- // X, Y, Z, U, V
- -1.0f, -1.0f, 0, 1.f, 0.f,
- 1.0f, -1.0f, 0, 0.f, 0.f,
- -1.0f, 1.0f, 0, 1.f, 1.f,
- 1.0f, 1.0f, 0, 0.f, 1.f,
- };
-
- // Sampling is mirrored across the vertical axis
- private static final float[] sVerticalFlipTriangleVertices = {
- // X, Y, Z, U, V
- -1.0f, -1.0f, 0, 0.f, 1.f,
- 1.0f, -1.0f, 0, 1.f, 1.f,
- -1.0f, 1.0f, 0, 0.f, 0.f,
- 1.0f, 1.0f, 0, 1.f, 0.f,
- };
-
- // Sampling is mirrored across the both axes
- private static final float[] sBothFlipTriangleVertices = {
- // X, Y, Z, U, V
- -1.0f, -1.0f, 0, 1.f, 1.f,
- 1.0f, -1.0f, 0, 0.f, 1.f,
- -1.0f, 1.0f, 0, 1.f, 0.f,
- 1.0f, 1.0f, 0, 0.f, 0.f,
- };
-
- // Sampling is 1:1 for a straight copy for the back camera
- private static final float[] sRegularTriangleVertices = {
- // X, Y, Z, U, V
- -1.0f, -1.0f, 0, 0.f, 0.f,
- 1.0f, -1.0f, 0, 1.f, 0.f,
- -1.0f, 1.0f, 0, 0.f, 1.f,
- 1.0f, 1.0f, 0, 1.f, 1.f,
- };
-
- private FloatBuffer mRegularTriangleVertices;
- private FloatBuffer mHorizontalFlipTriangleVertices;
- private FloatBuffer mVerticalFlipTriangleVertices;
- private FloatBuffer mBothFlipTriangleVertices;
- private final int mFacing;
-
- /**
- * As used in this file, this vertex shader maps a unit square to the view, and
- * tells the fragment shader to interpolate over it. Each surface pixel position
- * is mapped to a 2D homogeneous texture coordinate of the form (s, t, 0, 1) with
- * s and t in the inclusive range [0, 1], and the matrix from
- * {@link SurfaceTexture#getTransformMatrix(float[])} is used to map this
- * coordinate to a texture location.
- */
- private static final String VERTEX_SHADER =
- "uniform mat4 uMVPMatrix;\n" +
- "uniform mat4 uSTMatrix;\n" +
- "attribute vec4 aPosition;\n" +
- "attribute vec4 aTextureCoord;\n" +
- "varying vec2 vTextureCoord;\n" +
- "void main() {\n" +
- " gl_Position = uMVPMatrix * aPosition;\n" +
- " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
- "}\n";
-
- /**
- * This fragment shader simply draws the color in the 2D texture at
- * the location from the {@code VERTEX_SHADER}.
- */
- private static final String FRAGMENT_SHADER =
- "#extension GL_OES_EGL_image_external : require\n" +
- "precision mediump float;\n" +
- "varying vec2 vTextureCoord;\n" +
- "uniform samplerExternalOES sTexture;\n" +
- "void main() {\n" +
- " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
- "}\n";
-
- private float[] mMVPMatrix = new float[GL_MATRIX_SIZE];
- private float[] mSTMatrix = new float[GL_MATRIX_SIZE];
-
- private int mProgram;
- private int mTextureID = 0;
- private int muMVPMatrixHandle;
- private int muSTMatrixHandle;
- private int maPositionHandle;
- private int maTextureHandle;
-
- private PerfMeasurement mPerfMeasurer = null;
- private static final String LEGACY_PERF_PROPERTY = "persist.camera.legacy_perf";
-
- public SurfaceTextureRenderer(int facing) {
- mFacing = facing;
-
- mRegularTriangleVertices = ByteBuffer.allocateDirect(sRegularTriangleVertices.length *
- FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
- mRegularTriangleVertices.put(sRegularTriangleVertices).position(0);
-
- mHorizontalFlipTriangleVertices = ByteBuffer.allocateDirect(
- sHorizontalFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
- order(ByteOrder.nativeOrder()).asFloatBuffer();
- mHorizontalFlipTriangleVertices.put(sHorizontalFlipTriangleVertices).position(0);
-
- mVerticalFlipTriangleVertices = ByteBuffer.allocateDirect(
- sVerticalFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
- order(ByteOrder.nativeOrder()).asFloatBuffer();
- mVerticalFlipTriangleVertices.put(sVerticalFlipTriangleVertices).position(0);
-
- mBothFlipTriangleVertices = ByteBuffer.allocateDirect(
- sBothFlipTriangleVertices.length * FLOAT_SIZE_BYTES).
- order(ByteOrder.nativeOrder()).asFloatBuffer();
- mBothFlipTriangleVertices.put(sBothFlipTriangleVertices).position(0);
-
- Matrix.setIdentityM(mSTMatrix, 0);
- }
-
- private int loadShader(int shaderType, String source) {
- int shader = GLES20.glCreateShader(shaderType);
- checkGlError("glCreateShader type=" + shaderType);
- GLES20.glShaderSource(shader, source);
- GLES20.glCompileShader(shader);
- int[] compiled = new int[1];
- GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
- if (compiled[0] == 0) {
- Log.e(TAG, "Could not compile shader " + shaderType + ":");
- Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
- GLES20.glDeleteShader(shader);
- // TODO: handle this more gracefully
- throw new IllegalStateException("Could not compile shader " + shaderType);
- }
- return shader;
- }
-
- private int createProgram(String vertexSource, String fragmentSource) {
- int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
- if (vertexShader == 0) {
- return 0;
- }
- int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
- if (pixelShader == 0) {
- return 0;
- }
-
- int program = GLES20.glCreateProgram();
- checkGlError("glCreateProgram");
- if (program == 0) {
- Log.e(TAG, "Could not create program");
- }
- GLES20.glAttachShader(program, vertexShader);
- checkGlError("glAttachShader");
- GLES20.glAttachShader(program, pixelShader);
- checkGlError("glAttachShader");
- GLES20.glLinkProgram(program);
- int[] linkStatus = new int[1];
- GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
- if (linkStatus[0] != GLES20.GL_TRUE) {
- Log.e(TAG, "Could not link program: ");
- Log.e(TAG, GLES20.glGetProgramInfoLog(program));
- GLES20.glDeleteProgram(program);
- // TODO: handle this more gracefully
- throw new IllegalStateException("Could not link program");
- }
- return program;
- }
-
- private void drawFrame(SurfaceTexture st, int width, int height, int flipType)
- throws LegacyExceptionUtils.BufferQueueAbandonedException {
- checkGlError("onDrawFrame start");
- st.getTransformMatrix(mSTMatrix);
-
- Matrix.setIdentityM(mMVPMatrix, /*smOffset*/0);
-
- // Find intermediate buffer dimensions
- Size dimens;
- try {
- dimens = LegacyCameraDevice.getTextureSize(st);
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- // Should never hit this.
- throw new IllegalStateException("Surface abandoned, skipping drawFrame...", e);
- }
- float texWidth = dimens.getWidth();
- float texHeight = dimens.getHeight();
-
- if (texWidth <= 0 || texHeight <= 0) {
- throw new IllegalStateException("Illegal intermediate texture with dimension of 0");
- }
-
- // Letterbox or pillar-box output dimensions into intermediate dimensions.
- RectF intermediate = new RectF(/*left*/0, /*top*/0, /*right*/texWidth, /*bottom*/texHeight);
- RectF output = new RectF(/*left*/0, /*top*/0, /*right*/width, /*bottom*/height);
- android.graphics.Matrix boxingXform = new android.graphics.Matrix();
- boxingXform.setRectToRect(output, intermediate, android.graphics.Matrix.ScaleToFit.CENTER);
- boxingXform.mapRect(output);
-
- // Find scaling factor from pillar-boxed/letter-boxed output dimensions to intermediate
- // buffer dimensions.
- float scaleX = intermediate.width() / output.width();
- float scaleY = intermediate.height() / output.height();
-
- // Intermediate texture is implicitly scaled to 'fill' the output dimensions in clip space
- // coordinates in the shader. To avoid stretching, we need to scale the larger dimension
- // of the intermediate buffer so that the output buffer is actually letter-boxed
- // or pillar-boxed into the intermediate buffer after clipping.
- Matrix.scaleM(mMVPMatrix, /*offset*/0, /*x*/scaleX, /*y*/scaleY, /*z*/1);
-
- if (DEBUG) {
- Log.d(TAG, "Scaling factors (S_x = " + scaleX + ",S_y = " + scaleY + ") used for " +
- width + "x" + height + " surface, intermediate buffer size is " + texWidth +
- "x" + texHeight);
- }
-
- // Set viewport to be output buffer dimensions
- GLES20.glViewport(0, 0, width, height);
-
- if (DEBUG) {
- GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
- GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
- }
-
- GLES20.glUseProgram(mProgram);
- checkGlError("glUseProgram");
-
- GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
- GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
-
- FloatBuffer triangleVertices;
- switch(flipType) {
- case FLIP_TYPE_HORIZONTAL:
- triangleVertices = mHorizontalFlipTriangleVertices;
- break;
- case FLIP_TYPE_VERTICAL:
- triangleVertices = mVerticalFlipTriangleVertices;
- break;
- case FLIP_TYPE_BOTH:
- triangleVertices = mBothFlipTriangleVertices;
- break;
- default:
- triangleVertices = mRegularTriangleVertices;
- break;
- }
-
- triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
- GLES20.glVertexAttribPointer(maPositionHandle, VERTEX_POS_SIZE, GLES20.GL_FLOAT,
- /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
- checkGlError("glVertexAttribPointer maPosition");
- GLES20.glEnableVertexAttribArray(maPositionHandle);
- checkGlError("glEnableVertexAttribArray maPositionHandle");
-
- triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
- GLES20.glVertexAttribPointer(maTextureHandle, VERTEX_UV_SIZE, GLES20.GL_FLOAT,
- /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
- checkGlError("glVertexAttribPointer maTextureHandle");
- GLES20.glEnableVertexAttribArray(maTextureHandle);
- checkGlError("glEnableVertexAttribArray maTextureHandle");
-
- GLES20.glUniformMatrix4fv(muMVPMatrixHandle, /*count*/ 1, /*transpose*/ false, mMVPMatrix,
- /*offset*/ 0);
- GLES20.glUniformMatrix4fv(muSTMatrixHandle, /*count*/ 1, /*transpose*/ false, mSTMatrix,
- /*offset*/ 0);
-
- GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /*offset*/ 0, /*count*/ 4);
- checkGlDrawError("glDrawArrays");
- }
-
- /**
- * Initializes GL state. Call this after the EGL surface has been created and made current.
- */
- private void initializeGLState() {
- mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
- if (mProgram == 0) {
- throw new IllegalStateException("failed creating program");
- }
- maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
- checkGlError("glGetAttribLocation aPosition");
- if (maPositionHandle == -1) {
- throw new IllegalStateException("Could not get attrib location for aPosition");
- }
- maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
- checkGlError("glGetAttribLocation aTextureCoord");
- if (maTextureHandle == -1) {
- throw new IllegalStateException("Could not get attrib location for aTextureCoord");
- }
-
- muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
- checkGlError("glGetUniformLocation uMVPMatrix");
- if (muMVPMatrixHandle == -1) {
- throw new IllegalStateException("Could not get attrib location for uMVPMatrix");
- }
-
- muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
- checkGlError("glGetUniformLocation uSTMatrix");
- if (muSTMatrixHandle == -1) {
- throw new IllegalStateException("Could not get attrib location for uSTMatrix");
- }
-
- int[] textures = new int[1];
- GLES20.glGenTextures(/*n*/ 1, textures, /*offset*/ 0);
-
- mTextureID = textures[0];
- GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
- checkGlError("glBindTexture mTextureID");
-
- GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
- GLES20.GL_NEAREST);
- GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
- GLES20.GL_LINEAR);
- GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
- GLES20.GL_CLAMP_TO_EDGE);
- GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
- GLES20.GL_CLAMP_TO_EDGE);
- checkGlError("glTexParameter");
- }
-
- private int getTextureId() {
- return mTextureID;
- }
-
- private void clearState() {
- mSurfaces.clear();
- for (EGLSurfaceHolder holder : mConversionSurfaces) {
- try {
- LegacyCameraDevice.disconnectSurface(holder.surface);
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.w(TAG, "Surface abandoned, skipping...", e);
- }
- }
- mConversionSurfaces.clear();
- mPBufferPixels = null;
- if (mSurfaceTexture != null) {
- mSurfaceTexture.release();
- }
- mSurfaceTexture = null;
- }
-
- private void configureEGLContext() {
- mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
- if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
- throw new IllegalStateException("No EGL14 display");
- }
- int[] version = new int[2];
- if (!EGL14.eglInitialize(mEGLDisplay, version, /*offset*/ 0, version, /*offset*/ 1)) {
- throw new IllegalStateException("Cannot initialize EGL14");
- }
-
- int[] attribList = {
- EGL14.EGL_RED_SIZE, EGL_COLOR_BITLENGTH,
- EGL14.EGL_GREEN_SIZE, EGL_COLOR_BITLENGTH,
- EGL14.EGL_BLUE_SIZE, EGL_COLOR_BITLENGTH,
- EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
- EGL_RECORDABLE_ANDROID, 1,
- EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT | EGL14.EGL_WINDOW_BIT,
- EGL14.EGL_NONE
- };
- EGLConfig[] configs = new EGLConfig[1];
- int[] numConfigs = new int[1];
- EGL14.eglChooseConfig(mEGLDisplay, attribList, /*offset*/ 0, configs, /*offset*/ 0,
- configs.length, numConfigs, /*offset*/ 0);
- checkEglError("eglCreateContext RGB888+recordable ES2");
- mConfigs = configs[0];
- int[] attrib_list = {
- EGL14.EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION,
- EGL14.EGL_NONE
- };
- mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
- attrib_list, /*offset*/ 0);
- checkEglError("eglCreateContext");
- if(mEGLContext == EGL14.EGL_NO_CONTEXT) {
- throw new IllegalStateException("No EGLContext could be made");
- }
- }
-
- private void configureEGLOutputSurfaces(Collection<EGLSurfaceHolder> surfaces) {
- if (surfaces == null || surfaces.size() == 0) {
- throw new IllegalStateException("No Surfaces were provided to draw to");
- }
- int[] surfaceAttribs = {
- EGL14.EGL_NONE
- };
- for (EGLSurfaceHolder holder : surfaces) {
- holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs,
- holder.surface, surfaceAttribs, /*offset*/ 0);
- checkEglError("eglCreateWindowSurface");
- }
- }
-
- private void configureEGLPbufferSurfaces(Collection<EGLSurfaceHolder> surfaces) {
- if (surfaces == null || surfaces.size() == 0) {
- throw new IllegalStateException("No Surfaces were provided to draw to");
- }
-
- int maxLength = 0;
- for (EGLSurfaceHolder holder : surfaces) {
- int length = holder.width * holder.height;
- // Find max surface size, ensure PBuffer can hold this many pixels
- maxLength = (length > maxLength) ? length : maxLength;
- int[] surfaceAttribs = {
- EGL14.EGL_WIDTH, holder.width,
- EGL14.EGL_HEIGHT, holder.height,
- EGL14.EGL_NONE
- };
- holder.eglSurface =
- EGL14.eglCreatePbufferSurface(mEGLDisplay, mConfigs, surfaceAttribs, 0);
- checkEglError("eglCreatePbufferSurface");
- }
- mPBufferPixels = ByteBuffer.allocateDirect(maxLength * PBUFFER_PIXEL_BYTES)
- .order(ByteOrder.nativeOrder());
- }
-
- private void releaseEGLContext() {
- if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
- EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
- EGL14.EGL_NO_CONTEXT);
- dumpGlTiming();
- if (mSurfaces != null) {
- for (EGLSurfaceHolder holder : mSurfaces) {
- if (holder.eglSurface != null) {
- EGL14.eglDestroySurface(mEGLDisplay, holder.eglSurface);
- }
- }
- }
- if (mConversionSurfaces != null) {
- for (EGLSurfaceHolder holder : mConversionSurfaces) {
- if (holder.eglSurface != null) {
- EGL14.eglDestroySurface(mEGLDisplay, holder.eglSurface);
- }
- }
- }
- EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
- EGL14.eglReleaseThread();
- EGL14.eglTerminate(mEGLDisplay);
- }
-
- mConfigs = null;
- mEGLDisplay = EGL14.EGL_NO_DISPLAY;
- mEGLContext = EGL14.EGL_NO_CONTEXT;
- clearState();
- }
-
- private void makeCurrent(EGLSurface surface)
- throws LegacyExceptionUtils.BufferQueueAbandonedException {
- EGL14.eglMakeCurrent(mEGLDisplay, surface, surface, mEGLContext);
- checkEglDrawError("makeCurrent");
- }
-
- private boolean swapBuffers(EGLSurface surface)
- throws LegacyExceptionUtils.BufferQueueAbandonedException {
- boolean result = EGL14.eglSwapBuffers(mEGLDisplay, surface);
-
- int error = EGL14.eglGetError();
- switch (error) {
- case EGL14.EGL_SUCCESS:
- return result;
-
- // Check for an abandoned buffer queue, or other error conditions out
- // of the user's control.
- //
- // From the EGL 1.4 spec (2013-12-04), Section 3.9.4 Posting Errors:
- //
- // If eglSwapBuffers is called and the native window associated with
- // surface is no longer valid, an EGL_BAD_NATIVE_WINDOW error is
- // generated.
- //
- // We also interpret EGL_BAD_SURFACE as indicating an abandoned
- // surface, even though the EGL spec does not document it as such, for
- // backwards compatibility with older versions of this file.
- case EGL14.EGL_BAD_NATIVE_WINDOW:
- case EGL14.EGL_BAD_SURFACE:
- throw new LegacyExceptionUtils.BufferQueueAbandonedException();
-
- default:
- throw new IllegalStateException(
- "swapBuffers: EGL error: 0x" + Integer.toHexString(error));
- }
- }
-
- private void checkEglDrawError(String msg)
- throws LegacyExceptionUtils.BufferQueueAbandonedException {
- int error;
- if ((error = EGL14.eglGetError()) == EGL14.EGL_BAD_NATIVE_WINDOW) {
- throw new LegacyExceptionUtils.BufferQueueAbandonedException();
- }
- if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
- throw new IllegalStateException(msg + ": EGL error: 0x" + Integer.toHexString(error));
- }
- }
-
- private void checkEglError(String msg) {
- int error;
- if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
- throw new IllegalStateException(msg + ": EGL error: 0x" + Integer.toHexString(error));
- }
- }
-
- private void checkGlError(String msg) {
- int error;
- while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
- throw new IllegalStateException(
- msg + ": GLES20 error: 0x" + Integer.toHexString(error));
- }
- }
-
- private void checkGlDrawError(String msg)
- throws LegacyExceptionUtils.BufferQueueAbandonedException {
- int error;
- boolean surfaceAbandoned = false;
- boolean glError = false;
- while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
- if (error == GLES20.GL_OUT_OF_MEMORY) {
- surfaceAbandoned = true;
- } else {
- glError = true;
- }
- }
- if (glError) {
- throw new IllegalStateException(
- msg + ": GLES20 error: 0x" + Integer.toHexString(error));
- }
- if (surfaceAbandoned) {
- throw new LegacyExceptionUtils.BufferQueueAbandonedException();
- }
- }
-
- /**
- * Save a measurement dump to disk, in
- * {@code /sdcard/CameraLegacy/durations_<time>_<width1>x<height1>_...txt}
- */
- private void dumpGlTiming() {
- if (mPerfMeasurer == null) return;
-
- File legacyStorageDir = new File(Environment.getExternalStorageDirectory(), "CameraLegacy");
- if (!legacyStorageDir.exists()){
- if (!legacyStorageDir.mkdirs()){
- Log.e(TAG, "Failed to create directory for data dump");
- return;
- }
- }
-
- StringBuilder path = new StringBuilder(legacyStorageDir.getPath());
- path.append(File.separator);
- path.append("durations_");
-
- path.append(formatTimestamp(System.currentTimeMillis()));
- path.append("_S");
- for (EGLSurfaceHolder surface : mSurfaces) {
- path.append(String.format("_%d_%d", surface.width, surface.height));
- }
- path.append("_C");
- for (EGLSurfaceHolder surface : mConversionSurfaces) {
- path.append(String.format("_%d_%d", surface.width, surface.height));
- }
- path.append(".txt");
- mPerfMeasurer.dumpPerformanceData(path.toString());
- }
-
- private static String formatTimestamp(long timeMillis) {
- // This is a replacement for {@link Time#format2445()} that doesn't suffer from Y2038
- // issues.
- Instant instant = Instant.ofEpochMilli(timeMillis);
- ZoneId zoneId = ZoneId.systemDefault();
- LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zoneId);
- return LOG_NAME_TIME_FORMATTER.format(localDateTime);
- }
-
- private void setupGlTiming() {
- if (PerfMeasurement.isGlTimingSupported()) {
- Log.d(TAG, "Enabling GL performance measurement");
- mPerfMeasurer = new PerfMeasurement();
- } else {
- Log.d(TAG, "GL performance measurement not supported on this device");
- mPerfMeasurer = null;
- }
- }
-
- private void beginGlTiming() {
- if (mPerfMeasurer == null) return;
- mPerfMeasurer.startTimer();
- }
-
- private void addGlTimestamp(long timestamp) {
- if (mPerfMeasurer == null) return;
- mPerfMeasurer.addTimestamp(timestamp);
- }
-
- private void endGlTiming() {
- if (mPerfMeasurer == null) return;
- mPerfMeasurer.stopTimer();
- }
-
- /**
- * Return the surface texture to draw to - this is the texture use to when producing output
- * surface buffers.
- *
- * @return a {@link SurfaceTexture}.
- */
- public SurfaceTexture getSurfaceTexture() {
- return mSurfaceTexture;
- }
-
- /**
- * Set a collection of output {@link Surface}s that can be drawn to.
- *
- * @param surfaces a {@link Collection} of surfaces.
- */
- public void configureSurfaces(Collection<Pair<Surface, Size>> surfaces) {
- releaseEGLContext();
-
- if (surfaces == null || surfaces.size() == 0) {
- Log.w(TAG, "No output surfaces configured for GL drawing.");
- return;
- }
-
- for (Pair<Surface, Size> p : surfaces) {
- Surface s = p.first;
- Size surfaceSize = p.second;
- // If pixel conversions aren't handled by egl, use a pbuffer
- try {
- EGLSurfaceHolder holder = new EGLSurfaceHolder();
- holder.surface = s;
- holder.width = surfaceSize.getWidth();
- holder.height = surfaceSize.getHeight();
- if (LegacyCameraDevice.needsConversion(s)) {
- mConversionSurfaces.add(holder);
- // LegacyCameraDevice is the producer of surfaces if it's not handled by EGL,
- // so LegacyCameraDevice needs to connect to the surfaces.
- LegacyCameraDevice.connectSurface(s);
- } else {
- mSurfaces.add(holder);
- }
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.w(TAG, "Surface abandoned, skipping configuration... ", e);
- }
- }
-
- // Set up egl display
- configureEGLContext();
-
- // Set up regular egl surfaces if needed
- if (mSurfaces.size() > 0) {
- configureEGLOutputSurfaces(mSurfaces);
- }
-
- // Set up pbuffer surface if needed
- if (mConversionSurfaces.size() > 0) {
- configureEGLPbufferSurfaces(mConversionSurfaces);
- }
-
- try {
- makeCurrent((mSurfaces.size() > 0) ? mSurfaces.get(0).eglSurface :
- mConversionSurfaces.get(0).eglSurface);
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.w(TAG, "Surface abandoned, skipping configuration... ", e);
- }
-
- initializeGLState();
- mSurfaceTexture = new SurfaceTexture(getTextureId());
-
- // Set up performance tracking if enabled
- if (SystemProperties.getBoolean(LEGACY_PERF_PROPERTY, false)) {
- setupGlTiming();
- }
- }
-
- /**
- * Draw the current buffer in the {@link SurfaceTexture} returned from
- * {@link #getSurfaceTexture()} into the set of target {@link Surface}s
- * in the next request from the given {@link CaptureCollector}, or drop
- * the frame if none is available.
- *
- * <p>
- * Any {@link Surface}s targeted must be a subset of the {@link Surface}s
- * set in the last {@link #configureSurfaces(java.util.Collection)} call.
- * </p>
- *
- * @param targetCollector the surfaces to draw to.
- */
- public void drawIntoSurfaces(CaptureCollector targetCollector) {
- if ((mSurfaces == null || mSurfaces.size() == 0)
- && (mConversionSurfaces == null || mConversionSurfaces.size() == 0)) {
- return;
- }
-
- boolean doTiming = targetCollector.hasPendingPreviewCaptures();
- checkGlError("before updateTexImage");
-
- if (doTiming) {
- beginGlTiming();
- }
-
- mSurfaceTexture.updateTexImage();
-
- long timestamp = mSurfaceTexture.getTimestamp();
-
- Pair<RequestHolder, Long> captureHolder = targetCollector.previewCaptured(timestamp);
-
- // No preview request queued, drop frame.
- if (captureHolder == null) {
- if (DEBUG) {
- Log.d(TAG, "Dropping preview frame.");
- }
- if (doTiming) {
- endGlTiming();
- }
- return;
- }
-
- RequestHolder request = captureHolder.first;
-
- Collection<Surface> targetSurfaces = request.getHolderTargets();
- if (doTiming) {
- addGlTimestamp(timestamp);
- }
-
- List<Long> targetSurfaceIds = new ArrayList();
- try {
- targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces);
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.w(TAG, "Surface abandoned, dropping frame. ", e);
- request.setOutputAbandoned();
- }
-
- for (EGLSurfaceHolder holder : mSurfaces) {
- if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
- try{
- LegacyCameraDevice.setSurfaceDimens(holder.surface, holder.width,
- holder.height);
- makeCurrent(holder.eglSurface);
-
- LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
- drawFrame(mSurfaceTexture, holder.width, holder.height,
- (mFacing == CameraCharacteristics.LENS_FACING_FRONT) ?
- FLIP_TYPE_HORIZONTAL : FLIP_TYPE_NONE);
- swapBuffers(holder.eglSurface);
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.w(TAG, "Surface abandoned, dropping frame. ", e);
- request.setOutputAbandoned();
- }
- }
- }
- for (EGLSurfaceHolder holder : mConversionSurfaces) {
- if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
- // glReadPixels reads from the bottom of the buffer, so add an extra vertical flip
- try {
- makeCurrent(holder.eglSurface);
- drawFrame(mSurfaceTexture, holder.width, holder.height,
- (mFacing == CameraCharacteristics.LENS_FACING_FRONT) ?
- FLIP_TYPE_BOTH : FLIP_TYPE_VERTICAL);
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- // Should never hit this.
- throw new IllegalStateException("Surface abandoned, skipping drawFrame...", e);
- }
- mPBufferPixels.clear();
- GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height,
- GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
- checkGlError("glReadPixels");
-
- try {
- int format = LegacyCameraDevice.detectSurfaceType(holder.surface);
- LegacyCameraDevice.setSurfaceDimens(holder.surface, holder.width,
- holder.height);
- LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
- LegacyCameraDevice.produceFrame(holder.surface, mPBufferPixels.array(),
- holder.width, holder.height, format);
- } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
- Log.w(TAG, "Surface abandoned, dropping frame. ", e);
- request.setOutputAbandoned();
- }
- }
- }
- targetCollector.previewProduced();
-
- if (doTiming) {
- endGlTiming();
- }
- }
-
- /**
- * Clean up the current GL context.
- */
- public void cleanupEGLContext() {
- releaseEGLContext();
- }
-
- /**
- * Drop all current GL operations on the floor.
- */
- public void flush() {
- // TODO: implement flush
- Log.e(TAG, "Flush not yet implemented.");
- }
-}
diff --git a/core/java/android/hardware/camera2/legacy/package.html b/core/java/android/hardware/camera2/legacy/package.html
deleted file mode 100644
index db6f78b..0000000
--- a/core/java/android/hardware/camera2/legacy/package.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<body>
-{@hide}
-</body>
\ No newline at end of file
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index c37f9fe..52251ba 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -24,7 +24,6 @@
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.legacy.LegacyCameraDevice;
import android.hardware.camera2.utils.HashCodeHelpers;
import android.hardware.camera2.utils.SurfaceUtils;
import android.util.Range;
@@ -69,6 +68,8 @@
private static final String TAG = "StreamConfigurationMap";
+ private static final int MAX_DIMEN_FOR_ROUNDING = 1920; // maximum allowed width for rounding
+
/**
* Create a new {@link StreamConfigurationMap}.
*
@@ -568,7 +569,7 @@
if (config.getSize().equals(surfaceSize)) {
return true;
} else if (isFlexible &&
- (config.getSize().getWidth() <= LegacyCameraDevice.MAX_DIMEN_FOR_ROUNDING)) {
+ (config.getSize().getWidth() <= MAX_DIMEN_FOR_ROUNDING)) {
return true;
}
}
diff --git a/core/java/android/hardware/camera2/utils/SurfaceUtils.java b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
index abe1372..35b5c15 100644
--- a/core/java/android/hardware/camera2/utils/SurfaceUtils.java
+++ b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
@@ -16,10 +16,14 @@
package android.hardware.camera2.utils;
+import static android.system.OsConstants.EINVAL;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.ImageFormat;
-import android.hardware.camera2.legacy.LegacyCameraDevice;
-import android.hardware.camera2.legacy.LegacyExceptionUtils.BufferQueueAbandonedException;
+import android.graphics.PixelFormat;
+import android.hardware.HardwareBuffer;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.util.Range;
import android.util.Size;
@@ -35,6 +39,15 @@
*/
public class SurfaceUtils {
+ // Usage flags not yet included in HardwareBuffer
+ private static final int USAGE_RENDERSCRIPT = 0x00100000;
+ private static final int USAGE_HW_COMPOSER = 0x00000800;
+
+ // Image formats not yet included in PixelFormat
+ private static final int BGRA_8888 = 0x5;
+
+ private static final int BAD_VALUE = -EINVAL;
+
/**
* Check if a surface is for preview consumer based on consumer end point Gralloc usage flags.
*
@@ -42,7 +55,17 @@
* @return true if the surface is for preview consumer, false otherwise.
*/
public static boolean isSurfaceForPreview(Surface surface) {
- return LegacyCameraDevice.isPreviewConsumer(surface);
+ checkNotNull(surface);
+ long usageFlags = nativeDetectSurfaceUsageFlags(surface);
+ long disallowedFlags = HardwareBuffer.USAGE_VIDEO_ENCODE | USAGE_RENDERSCRIPT
+ | HardwareBuffer.USAGE_CPU_READ_OFTEN;
+ long allowedFlags = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | USAGE_HW_COMPOSER
+ | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT;
+ boolean previewConsumer = ((usageFlags & disallowedFlags) == 0
+ && (usageFlags & allowedFlags) != 0);
+ int surfaceFormat = getSurfaceFormat(surface);
+
+ return previewConsumer;
}
/**
@@ -53,7 +76,17 @@
* @return true if the surface is for hardware video encoder consumer, false otherwise.
*/
public static boolean isSurfaceForHwVideoEncoder(Surface surface) {
- return LegacyCameraDevice.isVideoEncoderConsumer(surface);
+ checkNotNull(surface);
+ long usageFlags = nativeDetectSurfaceUsageFlags(surface);
+ long disallowedFlags = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | USAGE_HW_COMPOSER
+ | USAGE_RENDERSCRIPT | HardwareBuffer.USAGE_CPU_READ_OFTEN;
+ long allowedFlags = HardwareBuffer.USAGE_VIDEO_ENCODE;
+ boolean videoEncoderConsumer = ((usageFlags & disallowedFlags) == 0
+ && (usageFlags & allowedFlags) != 0);
+
+ int surfaceFormat = getSurfaceFormat(surface);
+
+ return videoEncoderConsumer;
}
/**
@@ -63,9 +96,10 @@
* @return the native object id of the surface, 0 if surface is not backed by a native object.
*/
public static long getSurfaceId(Surface surface) {
+ checkNotNull(surface);
try {
- return LegacyCameraDevice.getSurfaceId(surface);
- } catch (BufferQueueAbandonedException e) {
+ return nativeGetSurfaceId(surface);
+ } catch (IllegalArgumentException e) {
return 0;
}
}
@@ -80,11 +114,13 @@
*/
@UnsupportedAppUsage
public static Size getSurfaceSize(Surface surface) {
- try {
- return LegacyCameraDevice.getSurfaceSize(surface);
- } catch (BufferQueueAbandonedException e) {
- throw new IllegalArgumentException("Surface was abandoned", e);
- }
+ checkNotNull(surface);
+
+ int[] dimens = new int[2];
+ int errorFlag = nativeDetectSurfaceDimens(surface, /*out*/dimens);
+ if (errorFlag == BAD_VALUE) throw new IllegalArgumentException("Surface was abandoned");
+
+ return new Size(dimens[0], dimens[1]);
}
/**
@@ -96,11 +132,17 @@
* @throws IllegalArgumentException if the surface is already abandoned.
*/
public static int getSurfaceFormat(Surface surface) {
- try {
- return LegacyCameraDevice.detectSurfaceType(surface);
- } catch (BufferQueueAbandonedException e) {
- throw new IllegalArgumentException("Surface was abandoned", e);
+ checkNotNull(surface);
+ int surfaceType = nativeDetectSurfaceType(surface);
+ if (surfaceType == BAD_VALUE) throw new IllegalArgumentException("Surface was abandoned");
+
+ // TODO: remove this override since the default format should be
+ // ImageFormat.PRIVATE. b/9487482
+ if ((surfaceType >= PixelFormat.RGBA_8888
+ && surfaceType <= BGRA_8888)) {
+ surfaceType = ImageFormat.PRIVATE;
}
+ return surfaceType;
}
/**
@@ -112,11 +154,10 @@
* @throws IllegalArgumentException if the surface is already abandoned.
*/
public static int getSurfaceDataspace(Surface surface) {
- try {
- return LegacyCameraDevice.detectSurfaceDataspace(surface);
- } catch (BufferQueueAbandonedException e) {
- throw new IllegalArgumentException("Surface was abandoned", e);
- }
+ checkNotNull(surface);
+ int dataSpace = nativeDetectSurfaceDataspace(surface);
+ if (dataSpace == BAD_VALUE) throw new IllegalArgumentException("Surface was abandoned");
+ return dataSpace;
}
/**
@@ -125,9 +166,21 @@
*
*/
public static boolean isFlexibleConsumer(Surface output) {
- return LegacyCameraDevice.isFlexibleConsumer(output);
+ checkNotNull(output);
+ long usageFlags = nativeDetectSurfaceUsageFlags(output);
+
+ // Keep up to date with allowed consumer types in
+ // frameworks/av/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+ long disallowedFlags = HardwareBuffer.USAGE_VIDEO_ENCODE | USAGE_RENDERSCRIPT;
+ long allowedFlags = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+ | HardwareBuffer.USAGE_CPU_READ_OFTEN
+ | USAGE_HW_COMPOSER;
+ boolean flexibleConsumer = ((usageFlags & disallowedFlags) == 0
+ && (usageFlags & allowedFlags) != 0);
+ return flexibleConsumer;
}
+
/**
* A high speed output surface can only be preview or hardware encoder surface.
*
@@ -209,4 +262,14 @@
}
}
+ private static native int nativeDetectSurfaceType(Surface surface);
+
+ private static native int nativeDetectSurfaceDataspace(Surface surface);
+
+ private static native long nativeDetectSurfaceUsageFlags(Surface surface);
+
+ private static native int nativeDetectSurfaceDimens(Surface surface,
+ /*out*/int[/*2*/] dimens);
+
+ private static native long nativeGetSurfaceId(Surface surface);
}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index c7fc2ad..b4546a4 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -592,7 +592,7 @@
new IBiometricServiceLockoutResetCallback.Stub() {
@Override
- public void onLockoutReset(IRemoteCallback serverCallback)
+ public void onLockoutReset(int sensorId, IRemoteCallback serverCallback)
throws RemoteException {
try {
final PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
@@ -601,7 +601,7 @@
wakeLock.acquire();
mHandler.post(() -> {
try {
- callback.onLockoutReset();
+ callback.onLockoutReset(sensorId);
} finally {
wakeLock.release();
}
@@ -978,7 +978,7 @@
* authentication
* again.
*/
- public void onLockoutReset() {
+ public void onLockoutReset(int sensorId) {
}
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 9dacca7..4ca75d9 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -347,7 +347,7 @@
* Called when lockout period expired and clients are allowed to listen for fingerprint
* again.
*/
- public void onLockoutReset() { }
+ public void onLockoutReset(int sensorId) { }
};
/**
@@ -751,7 +751,7 @@
new IBiometricServiceLockoutResetCallback.Stub() {
@Override
- public void onLockoutReset(IRemoteCallback serverCallback)
+ public void onLockoutReset(int sensorId, IRemoteCallback serverCallback)
throws RemoteException {
try {
final PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
@@ -759,7 +759,7 @@
wakeLock.acquire();
mHandler.post(() -> {
try {
- callback.onLockoutReset();
+ callback.onLockoutReset(sensorId);
} finally {
wakeLock.release();
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index ad58fea..38d7d2b 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -117,12 +117,6 @@
// Returns whether the specified sensor is an under-display fingerprint sensor (UDFPS).
boolean isUdfps(int sensorId);
- // Shows the UDFPS overlay.
- void showUdfpsOverlay();
-
- // Hides the UDFPS overlay.
- void hideUdfpsOverlay();
-
// Sets the controller for managing the UDFPS overlay.
void setUdfpsOverlayController(in IUdfpsOverlayController controller);
}
diff --git a/core/java/android/inputmethodservice/InlineSuggestionSession.java b/core/java/android/inputmethodservice/InlineSuggestionSession.java
index 90d0ff0a..20bf6e0 100644
--- a/core/java/android/inputmethodservice/InlineSuggestionSession.java
+++ b/core/java/android/inputmethodservice/InlineSuggestionSession.java
@@ -152,7 +152,7 @@
try {
mCallback.onInlineSuggestionsSessionInvalidated();
} catch (RemoteException e) {
- Log.w(TAG, "onInlineSuggestionsSessionInvalidated() remote exception:" + e);
+ Log.w(TAG, "onInlineSuggestionsSessionInvalidated() remote exception", e);
}
if (mResponseCallback != null) {
consumeInlineSuggestionsResponse(EMPTY_RESPONSE);
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index ce6c0ff..d2fc1d3 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -32,6 +32,7 @@
@UnsupportedAppUsage
void releaseWakeLock(IBinder lock, int flags);
void updateWakeLockUids(IBinder lock, in int[] uids);
+ oneway void powerHint(int hintId, int data);
oneway void setPowerBoost(int boost, int durationMs);
oneway void setPowerMode(int mode, boolean enabled);
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index e30a409..653a559 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -196,6 +196,12 @@
public abstract void uidIdle(int uid);
/**
+ * The hintId sent through this method should be in-line with the
+ * PowerHint defined in android/hardware/power/<version 1.0 & up>/IPower.h
+ */
+ public abstract void powerHint(int hintId, int data);
+
+ /**
* Boost: It is sent when user interacting with the device, for example,
* touchscreen events are incoming.
* Defined in hardware/interfaces/power/aidl/android/hardware/power/Boost.aidl
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index 0863a81..683c747 100755
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -19,12 +19,12 @@
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.icu.text.DateTimePatternGenerator;
import android.provider.Settings;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
-import libcore.icu.ICU;
import libcore.icu.LocaleData;
import java.text.SimpleDateFormat;
@@ -251,7 +251,8 @@
* @return a string pattern suitable for use with {@link java.text.SimpleDateFormat}.
*/
public static String getBestDateTimePattern(Locale locale, String skeleton) {
- return ICU.getBestDateTimePattern(skeleton, locale);
+ DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(locale);
+ return dtpg.getBestPattern(skeleton);
}
/**
@@ -333,7 +334,52 @@
* order returned here.
*/
public static char[] getDateFormatOrder(Context context) {
- return ICU.getDateFormatOrder(getDateFormatString(context));
+ return getDateFormatOrder(getDateFormatString(context));
+ }
+
+ /**
+ * @hide Used by internal framework class {@link android.widget.DatePickerSpinnerDelegate}.
+ */
+ public static char[] getDateFormatOrder(String pattern) {
+ char[] result = new char[3];
+ int resultIndex = 0;
+ boolean sawDay = false;
+ boolean sawMonth = false;
+ boolean sawYear = false;
+
+ for (int i = 0; i < pattern.length(); ++i) {
+ char ch = pattern.charAt(i);
+ if (ch == 'd' || ch == 'L' || ch == 'M' || ch == 'y') {
+ if (ch == 'd' && !sawDay) {
+ result[resultIndex++] = 'd';
+ sawDay = true;
+ } else if ((ch == 'L' || ch == 'M') && !sawMonth) {
+ result[resultIndex++] = 'M';
+ sawMonth = true;
+ } else if ((ch == 'y') && !sawYear) {
+ result[resultIndex++] = 'y';
+ sawYear = true;
+ }
+ } else if (ch == 'G') {
+ // Ignore the era specifier, if present.
+ } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
+ throw new IllegalArgumentException("Bad pattern character '" + ch + "' in "
+ + pattern);
+ } else if (ch == '\'') {
+ if (i < pattern.length() - 1 && pattern.charAt(i + 1) == '\'') {
+ ++i;
+ } else {
+ i = pattern.indexOf('\'', i + 1);
+ if (i == -1) {
+ throw new IllegalArgumentException("Bad quoting in " + pattern);
+ }
+ ++i;
+ }
+ } else {
+ // Ignore spaces and punctuation.
+ }
+ }
+ return result;
}
private static String getDateFormatString(Context context) {
diff --git a/core/java/android/text/format/DateTimeFormat.java b/core/java/android/text/format/DateTimeFormat.java
new file mode 100644
index 0000000..064d717
--- /dev/null
+++ b/core/java/android/text/format/DateTimeFormat.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 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.text.format;
+
+import android.icu.text.DateFormat;
+import android.icu.text.DateTimePatternGenerator;
+import android.icu.text.DisplayContext;
+import android.icu.text.SimpleDateFormat;
+import android.icu.util.Calendar;
+import android.icu.util.ULocale;
+import android.util.LruCache;
+
+/**
+ * A formatter that outputs a single date/time.
+ *
+ * @hide
+ */
+class DateTimeFormat {
+ private static final FormatterCache CACHED_FORMATTERS = new FormatterCache();
+
+ static class FormatterCache extends LruCache<String, DateFormat> {
+ FormatterCache() {
+ super(8);
+ }
+ }
+
+ private DateTimeFormat() {
+ }
+
+ public static String format(ULocale icuLocale, Calendar time, int flags,
+ DisplayContext displayContext) {
+ String skeleton = DateUtilsBridge.toSkeleton(time, flags);
+ String key = skeleton + "\t" + icuLocale + "\t" + time.getTimeZone();
+ synchronized (CACHED_FORMATTERS) {
+ DateFormat formatter = CACHED_FORMATTERS.get(key);
+ if (formatter == null) {
+ DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(
+ icuLocale);
+ formatter = new SimpleDateFormat(generator.getBestPattern(skeleton), icuLocale);
+ CACHED_FORMATTERS.put(key, formatter);
+ }
+ formatter.setContext(displayContext);
+ return formatter.format(time);
+ }
+ }
+}
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index ce676e0..51bea77 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -29,7 +29,6 @@
import libcore.icu.DateIntervalFormat;
import libcore.icu.LocaleData;
-import libcore.icu.RelativeDateTimeFormatter;
import java.io.IOException;
import java.time.Instant;
diff --git a/core/java/android/text/format/DateUtilsBridge.java b/core/java/android/text/format/DateUtilsBridge.java
new file mode 100644
index 0000000..370d999
--- /dev/null
+++ b/core/java/android/text/format/DateUtilsBridge.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2015 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.text.format;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.icu.util.Calendar;
+import android.icu.util.GregorianCalendar;
+import android.icu.util.TimeZone;
+import android.icu.util.ULocale;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Common methods and constants for the various ICU formatters used to support {@link
+ * android.text.format.DateUtils}.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = PACKAGE)
+public final class DateUtilsBridge {
+ // These are all public API in DateUtils. There are others, but they're either for use with
+ // other methods (like FORMAT_ABBREV_RELATIVE), don't internationalize (like FORMAT_CAP_AMPM),
+ // or have never been implemented anyway.
+ public static final int FORMAT_SHOW_TIME = 0x00001;
+ public static final int FORMAT_SHOW_WEEKDAY = 0x00002;
+ public static final int FORMAT_SHOW_YEAR = 0x00004;
+ public static final int FORMAT_NO_YEAR = 0x00008;
+ public static final int FORMAT_SHOW_DATE = 0x00010;
+ public static final int FORMAT_NO_MONTH_DAY = 0x00020;
+ public static final int FORMAT_12HOUR = 0x00040;
+ public static final int FORMAT_24HOUR = 0x00080;
+ public static final int FORMAT_UTC = 0x02000;
+ public static final int FORMAT_ABBREV_TIME = 0x04000;
+ public static final int FORMAT_ABBREV_WEEKDAY = 0x08000;
+ public static final int FORMAT_ABBREV_MONTH = 0x10000;
+ public static final int FORMAT_NUMERIC_DATE = 0x20000;
+ public static final int FORMAT_ABBREV_RELATIVE = 0x40000;
+ public static final int FORMAT_ABBREV_ALL = 0x80000;
+
+ /**
+ * Creates an immutable ICU timezone backed by the specified libcore timezone data. At the time
+ * of writing the libcore implementation is faster but restricted to 1902 - 2038. Callers must
+ * not modify the {@code tz} after calling this method.
+ */
+ public static TimeZone icuTimeZone(java.util.TimeZone tz) {
+ TimeZone icuTimeZone = TimeZone.getTimeZone(tz.getID());
+ icuTimeZone.freeze(); // Optimization - allows the timezone to be copied cheaply.
+ return icuTimeZone;
+ }
+
+ /**
+ * Create a GregorianCalendar based on the arguments
+ */
+ public static Calendar createIcuCalendar(TimeZone icuTimeZone, ULocale icuLocale,
+ long timeInMillis) {
+ Calendar calendar = new GregorianCalendar(icuTimeZone, icuLocale);
+ calendar.setTimeInMillis(timeInMillis);
+ return calendar;
+ }
+
+ public static String toSkeleton(Calendar calendar, int flags) {
+ return toSkeleton(calendar, calendar, flags);
+ }
+
+ public static String toSkeleton(Calendar startCalendar, Calendar endCalendar, int flags) {
+ if ((flags & FORMAT_ABBREV_ALL) != 0) {
+ flags |= FORMAT_ABBREV_MONTH | FORMAT_ABBREV_TIME | FORMAT_ABBREV_WEEKDAY;
+ }
+
+ String monthPart = "MMMM";
+ if ((flags & FORMAT_NUMERIC_DATE) != 0) {
+ monthPart = "M";
+ } else if ((flags & FORMAT_ABBREV_MONTH) != 0) {
+ monthPart = "MMM";
+ }
+
+ String weekPart = "EEEE";
+ if ((flags & FORMAT_ABBREV_WEEKDAY) != 0) {
+ weekPart = "EEE";
+ }
+
+ String timePart = "j"; // "j" means choose 12 or 24 hour based on current locale.
+ if ((flags & FORMAT_24HOUR) != 0) {
+ timePart = "H";
+ } else if ((flags & FORMAT_12HOUR) != 0) {
+ timePart = "h";
+ }
+
+ // If we've not been asked to abbreviate times, or we're using the 24-hour clock (where it
+ // never makes sense to leave out the minutes), include minutes. This gets us times like
+ // "4 PM" while avoiding times like "16" (for "16:00").
+ if ((flags & FORMAT_ABBREV_TIME) == 0 || (flags & FORMAT_24HOUR) != 0) {
+ timePart += "m";
+ } else {
+ // Otherwise, we're abbreviating a 12-hour time, and should only show the minutes
+ // if they're not both "00".
+ if (!(onTheHour(startCalendar) && onTheHour(endCalendar))) {
+ timePart = timePart + "m";
+ }
+ }
+
+ if (fallOnDifferentDates(startCalendar, endCalendar)) {
+ flags |= FORMAT_SHOW_DATE;
+ }
+
+ if (fallInSameMonth(startCalendar, endCalendar) && (flags & FORMAT_NO_MONTH_DAY) != 0) {
+ flags &= (~FORMAT_SHOW_WEEKDAY);
+ flags &= (~FORMAT_SHOW_TIME);
+ }
+
+ if ((flags & (FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_SHOW_WEEKDAY)) == 0) {
+ flags |= FORMAT_SHOW_DATE;
+ }
+
+ // If we've been asked to show the date, work out whether we think we should show the year.
+ if ((flags & FORMAT_SHOW_DATE) != 0) {
+ if ((flags & FORMAT_SHOW_YEAR) != 0) {
+ // The caller explicitly wants us to show the year.
+ } else if ((flags & FORMAT_NO_YEAR) != 0) {
+ // The caller explicitly doesn't want us to show the year, even if we otherwise
+ // would.
+ } else if (!fallInSameYear(startCalendar, endCalendar) || !isThisYear(startCalendar)) {
+ flags |= FORMAT_SHOW_YEAR;
+ }
+ }
+
+ StringBuilder builder = new StringBuilder();
+ if ((flags & (FORMAT_SHOW_DATE | FORMAT_NO_MONTH_DAY)) != 0) {
+ if ((flags & FORMAT_SHOW_YEAR) != 0) {
+ builder.append("y");
+ }
+ builder.append(monthPart);
+ if ((flags & FORMAT_NO_MONTH_DAY) == 0) {
+ builder.append("d");
+ }
+ }
+ if ((flags & FORMAT_SHOW_WEEKDAY) != 0) {
+ builder.append(weekPart);
+ }
+ if ((flags & FORMAT_SHOW_TIME) != 0) {
+ builder.append(timePart);
+ }
+ return builder.toString();
+ }
+
+ public static int dayDistance(Calendar c1, Calendar c2) {
+ return c2.get(Calendar.JULIAN_DAY) - c1.get(Calendar.JULIAN_DAY);
+ }
+
+ /**
+ * Returns whether the argument will be displayed as if it were midnight, using any of the
+ * skeletons provided by {@link #toSkeleton}.
+ */
+ public static boolean isDisplayMidnightUsingSkeleton(Calendar c) {
+ // All the skeletons returned by toSkeleton have minute precision (they may abbreviate
+ // 4:00 PM to 4 PM but will still show the following minute as 4:01 PM).
+ return c.get(Calendar.HOUR_OF_DAY) == 0 && c.get(Calendar.MINUTE) == 0;
+ }
+
+ private static boolean onTheHour(Calendar c) {
+ return c.get(Calendar.MINUTE) == 0 && c.get(Calendar.SECOND) == 0;
+ }
+
+ private static boolean fallOnDifferentDates(Calendar c1, Calendar c2) {
+ return c1.get(Calendar.YEAR) != c2.get(Calendar.YEAR)
+ || c1.get(Calendar.MONTH) != c2.get(Calendar.MONTH)
+ || c1.get(Calendar.DAY_OF_MONTH) != c2.get(Calendar.DAY_OF_MONTH);
+ }
+
+ private static boolean fallInSameMonth(Calendar c1, Calendar c2) {
+ return c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH);
+ }
+
+ private static boolean fallInSameYear(Calendar c1, Calendar c2) {
+ return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR);
+ }
+
+ private static boolean isThisYear(Calendar c) {
+ Calendar now = (Calendar) c.clone();
+ now.setTimeInMillis(System.currentTimeMillis());
+ return c.get(Calendar.YEAR) == now.get(Calendar.YEAR);
+ }
+}
diff --git a/core/java/android/text/format/RelativeDateTimeFormatter.java b/core/java/android/text/format/RelativeDateTimeFormatter.java
new file mode 100644
index 0000000..c5bca17
--- /dev/null
+++ b/core/java/android/text/format/RelativeDateTimeFormatter.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2015 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.text.format;
+
+import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_ALL;
+import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_MONTH;
+import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_RELATIVE;
+import static android.text.format.DateUtilsBridge.FORMAT_NO_YEAR;
+import static android.text.format.DateUtilsBridge.FORMAT_NUMERIC_DATE;
+import static android.text.format.DateUtilsBridge.FORMAT_SHOW_DATE;
+import static android.text.format.DateUtilsBridge.FORMAT_SHOW_TIME;
+import static android.text.format.DateUtilsBridge.FORMAT_SHOW_YEAR;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.icu.text.DisplayContext;
+import android.icu.util.Calendar;
+import android.icu.util.ULocale;
+import android.util.LruCache;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Locale;
+
+/**
+ * Exposes icu4j's RelativeDateTimeFormatter.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = PACKAGE)
+public final class RelativeDateTimeFormatter {
+
+ public static final long SECOND_IN_MILLIS = 1000;
+ public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
+ public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
+ public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
+ public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7;
+ // YEAR_IN_MILLIS considers 364 days as a year. However, since this
+ // constant comes from public API in DateUtils, it cannot be fixed here.
+ public static final long YEAR_IN_MILLIS = WEEK_IN_MILLIS * 52;
+
+ private static final int DAY_IN_MS = 24 * 60 * 60 * 1000;
+ private static final int EPOCH_JULIAN_DAY = 2440588;
+
+ private static final FormatterCache CACHED_FORMATTERS = new FormatterCache();
+
+ static class FormatterCache
+ extends LruCache<String, android.icu.text.RelativeDateTimeFormatter> {
+ FormatterCache() {
+ super(8);
+ }
+ }
+
+ private RelativeDateTimeFormatter() {
+ }
+
+ /**
+ * This is the internal API that implements the functionality of DateUtils
+ * .getRelativeTimeSpanString(long,
+ * long, long, int), which is to return a string describing 'time' as a time relative to 'now'
+ * such as '5 minutes ago', or 'In 2 days'. More examples can be found in DateUtils' doc.
+ * <p>
+ * In the implementation below, it selects the appropriate time unit based on the elapsed time
+ * between time' and 'now', e.g. minutes, days and etc. Callers may also specify the desired
+ * minimum resolution to show in the result. For example, '45 minutes ago' will become '0 hours
+ * ago' when minResolution is HOUR_IN_MILLIS. Once getting the quantity and unit to display, it
+ * calls icu4j's RelativeDateTimeFormatter to format the actual string according to the given
+ * locale.
+ * <p>
+ * Note that when minResolution is set to DAY_IN_MILLIS, it returns the result depending on the
+ * actual date difference. For example, it will return 'Yesterday' even if 'time' was less than
+ * 24 hours ago but falling onto a different calendar day.
+ * <p>
+ * It takes two additional parameters of Locale and TimeZone than the DateUtils' API. Caller
+ * must specify the locale and timezone. FORMAT_ABBREV_RELATIVE or FORMAT_ABBREV_ALL can be set
+ * in 'flags' to get the abbreviated forms when available. When 'time' equals to 'now', it
+ * always // returns a string like '0 seconds/minutes/... ago' according to minResolution.
+ */
+ public static String getRelativeTimeSpanString(Locale locale, java.util.TimeZone tz, long time,
+ long now, long minResolution, int flags) {
+ // Android has been inconsistent about capitalization in the past. e.g. bug
+ // http://b/20247811.
+ // Now we capitalize everything consistently.
+ final DisplayContext displayContext =
+ DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE;
+ return getRelativeTimeSpanString(locale, tz, time, now, minResolution, flags,
+ displayContext);
+ }
+
+ public static String getRelativeTimeSpanString(Locale locale, java.util.TimeZone tz, long time,
+ long now, long minResolution, int flags, DisplayContext displayContext) {
+ if (locale == null) {
+ throw new NullPointerException("locale == null");
+ }
+ if (tz == null) {
+ throw new NullPointerException("tz == null");
+ }
+ ULocale icuLocale = ULocale.forLocale(locale);
+ android.icu.util.TimeZone icuTimeZone = DateUtilsBridge.icuTimeZone(tz);
+ return getRelativeTimeSpanString(icuLocale, icuTimeZone, time, now, minResolution, flags,
+ displayContext);
+ }
+
+ private static String getRelativeTimeSpanString(ULocale icuLocale,
+ android.icu.util.TimeZone icuTimeZone, long time, long now, long minResolution,
+ int flags,
+ DisplayContext displayContext) {
+
+ long duration = Math.abs(now - time);
+ boolean past = (now >= time);
+
+ android.icu.text.RelativeDateTimeFormatter.Style style;
+ if ((flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0) {
+ style = android.icu.text.RelativeDateTimeFormatter.Style.SHORT;
+ } else {
+ style = android.icu.text.RelativeDateTimeFormatter.Style.LONG;
+ }
+
+ android.icu.text.RelativeDateTimeFormatter.Direction direction;
+ if (past) {
+ direction = android.icu.text.RelativeDateTimeFormatter.Direction.LAST;
+ } else {
+ direction = android.icu.text.RelativeDateTimeFormatter.Direction.NEXT;
+ }
+
+ // 'relative' defaults to true as we are generating relative time span
+ // string. It will be set to false when we try to display strings without
+ // a quantity, such as 'Yesterday', etc.
+ boolean relative = true;
+ int count;
+ android.icu.text.RelativeDateTimeFormatter.RelativeUnit unit;
+ android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit aunit = null;
+
+ if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) {
+ count = (int) (duration / SECOND_IN_MILLIS);
+ unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.SECONDS;
+ } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) {
+ count = (int) (duration / MINUTE_IN_MILLIS);
+ unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.MINUTES;
+ } else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) {
+ // Even if 'time' actually happened yesterday, we don't format it as
+ // "Yesterday" in this case. Unless the duration is longer than a day,
+ // or minResolution is specified as DAY_IN_MILLIS by user.
+ count = (int) (duration / HOUR_IN_MILLIS);
+ unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.HOURS;
+ } else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) {
+ count = Math.abs(dayDistance(icuTimeZone, time, now));
+ unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.DAYS;
+
+ if (count == 2) {
+ // Some locales have special terms for "2 days ago". Return them if
+ // available. Note that we cannot set up direction and unit here and
+ // make it fall through to use the call near the end of the function,
+ // because for locales that don't have special terms for "2 days ago",
+ // icu4j returns an empty string instead of falling back to strings
+ // like "2 days ago".
+ String str;
+ if (past) {
+ synchronized (CACHED_FORMATTERS) {
+ str = getFormatter(icuLocale, style, displayContext).format(
+ android.icu.text.RelativeDateTimeFormatter.Direction.LAST_2,
+ android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY);
+ }
+ } else {
+ synchronized (CACHED_FORMATTERS) {
+ str = getFormatter(icuLocale, style, displayContext).format(
+ android.icu.text.RelativeDateTimeFormatter.Direction.NEXT_2,
+ android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY);
+ }
+ }
+ if (str != null && !str.isEmpty()) {
+ return str;
+ }
+ // Fall back to show something like "2 days ago".
+ } else if (count == 1) {
+ // Show "Yesterday / Tomorrow" instead of "1 day ago / In 1 day".
+ aunit = android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY;
+ relative = false;
+ } else if (count == 0) {
+ // Show "Today" if time and now are on the same day.
+ aunit = android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY;
+ direction = android.icu.text.RelativeDateTimeFormatter.Direction.THIS;
+ relative = false;
+ }
+ } else if (minResolution == WEEK_IN_MILLIS) {
+ count = (int) (duration / WEEK_IN_MILLIS);
+ unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.WEEKS;
+ } else {
+ Calendar timeCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, time);
+ // The duration is longer than a week and minResolution is not
+ // WEEK_IN_MILLIS. Return the absolute date instead of relative time.
+
+ // Bug 19822016:
+ // If user doesn't supply the year display flag, we need to explicitly
+ // set that to show / hide the year based on time and now. Otherwise
+ // formatDateRange() would determine that based on the current system
+ // time and may give wrong results.
+ if ((flags & (FORMAT_NO_YEAR | FORMAT_SHOW_YEAR)) == 0) {
+ Calendar nowCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale,
+ now);
+
+ if (timeCalendar.get(Calendar.YEAR) != nowCalendar.get(Calendar.YEAR)) {
+ flags |= FORMAT_SHOW_YEAR;
+ } else {
+ flags |= FORMAT_NO_YEAR;
+ }
+ }
+ return DateTimeFormat.format(icuLocale, timeCalendar, flags, displayContext);
+ }
+
+ synchronized (CACHED_FORMATTERS) {
+ android.icu.text.RelativeDateTimeFormatter formatter =
+ getFormatter(icuLocale, style, displayContext);
+ if (relative) {
+ return formatter.format(count, direction, unit);
+ } else {
+ return formatter.format(direction, aunit);
+ }
+ }
+ }
+
+ /**
+ * This is the internal API that implements DateUtils.getRelativeDateTimeString(long, long,
+ * long, long, int), which is to return a string describing 'time' as a time relative to 'now',
+ * formatted like '[relative time/date], [time]'. More examples can be found in DateUtils' doc.
+ * <p>
+ * The function is similar to getRelativeTimeSpanString, but it always appends the absolute time
+ * to the relative time string to return '[relative time/date clause], [absolute time clause]'.
+ * It also takes an extra parameter transitionResolution to determine the format of the date
+ * clause. When the elapsed time is less than the transition resolution, it displays the
+ * relative time string. Otherwise, it gives the absolute numeric date string as the date
+ * clause. With the date and time clauses, it relies on icu4j's
+ * RelativeDateTimeFormatter::combineDateAndTime()
+ * to concatenate the two.
+ * <p>
+ * It takes two additional parameters of Locale and TimeZone than the DateUtils' API. Caller
+ * must specify the locale and timezone. FORMAT_ABBREV_RELATIVE or FORMAT_ABBREV_ALL can be set
+ * in 'flags' to get the abbreviated forms when they are available.
+ * <p>
+ * Bug 5252772: Since the absolute time will always be part of the result, minResolution will be
+ * set to at least DAY_IN_MILLIS to correctly indicate the date difference. For example, when
+ * it's 1:30 AM, it will return 'Yesterday, 11:30 PM' for getRelativeDateTimeString(null, null,
+ * now - 2 hours, now, HOUR_IN_MILLIS, DAY_IN_MILLIS, 0), instead of '2 hours ago, 11:30 PM'
+ * even with minResolution being HOUR_IN_MILLIS.
+ */
+ public static String getRelativeDateTimeString(Locale locale, java.util.TimeZone tz, long time,
+ long now, long minResolution, long transitionResolution, int flags) {
+
+ if (locale == null) {
+ throw new NullPointerException("locale == null");
+ }
+ if (tz == null) {
+ throw new NullPointerException("tz == null");
+ }
+ ULocale icuLocale = ULocale.forLocale(locale);
+ android.icu.util.TimeZone icuTimeZone = DateUtilsBridge.icuTimeZone(tz);
+
+ long duration = Math.abs(now - time);
+ // It doesn't make much sense to have results like: "1 week ago, 10:50 AM".
+ if (transitionResolution > WEEK_IN_MILLIS) {
+ transitionResolution = WEEK_IN_MILLIS;
+ }
+ android.icu.text.RelativeDateTimeFormatter.Style style;
+ if ((flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0) {
+ style = android.icu.text.RelativeDateTimeFormatter.Style.SHORT;
+ } else {
+ style = android.icu.text.RelativeDateTimeFormatter.Style.LONG;
+ }
+
+ Calendar timeCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, time);
+ Calendar nowCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, now);
+
+ int days = Math.abs(DateUtilsBridge.dayDistance(timeCalendar, nowCalendar));
+
+ // Now get the date clause, either in relative format or the actual date.
+ String dateClause;
+ if (duration < transitionResolution) {
+ // This is to fix bug 5252772. If there is any date difference, we should
+ // promote the minResolution to DAY_IN_MILLIS so that it can display the
+ // date instead of "x hours/minutes ago, [time]".
+ if (days > 0 && minResolution < DAY_IN_MILLIS) {
+ minResolution = DAY_IN_MILLIS;
+ }
+ dateClause = getRelativeTimeSpanString(icuLocale, icuTimeZone, time, now, minResolution,
+ flags, DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE);
+ } else {
+ // We always use fixed flags to format the date clause. User-supplied
+ // flags are ignored.
+ if (timeCalendar.get(Calendar.YEAR) != nowCalendar.get(Calendar.YEAR)) {
+ // Different years
+ flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE;
+ } else {
+ // Default
+ flags = FORMAT_SHOW_DATE | FORMAT_NO_YEAR | FORMAT_ABBREV_MONTH;
+ }
+
+ dateClause = DateTimeFormat.format(icuLocale, timeCalendar, flags,
+ DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE);
+ }
+
+ String timeClause = DateTimeFormat.format(icuLocale, timeCalendar, FORMAT_SHOW_TIME,
+ DisplayContext.CAPITALIZATION_NONE);
+
+ // icu4j also has other options available to control the capitalization. We are currently
+ // using
+ // the _NONE option only.
+ DisplayContext capitalizationContext = DisplayContext.CAPITALIZATION_NONE;
+
+ // Combine the two clauses, such as '5 days ago, 10:50 AM'.
+ synchronized (CACHED_FORMATTERS) {
+ return getFormatter(icuLocale, style, capitalizationContext)
+ .combineDateAndTime(dateClause, timeClause);
+ }
+ }
+
+ /**
+ * getFormatter() caches the RelativeDateTimeFormatter instances based on the combination of
+ * localeName, sytle and capitalizationContext. It should always be used along with the action
+ * of the formatter in a synchronized block, because otherwise the formatter returned by
+ * getFormatter() may have been evicted by the time of the call to formatter->action().
+ */
+ private static android.icu.text.RelativeDateTimeFormatter getFormatter(
+ ULocale locale, android.icu.text.RelativeDateTimeFormatter.Style style,
+ DisplayContext displayContext) {
+ String key = locale + "\t" + style + "\t" + displayContext;
+ android.icu.text.RelativeDateTimeFormatter formatter = CACHED_FORMATTERS.get(key);
+ if (formatter == null) {
+ formatter = android.icu.text.RelativeDateTimeFormatter.getInstance(
+ locale, null, style, displayContext);
+ CACHED_FORMATTERS.put(key, formatter);
+ }
+ return formatter;
+ }
+
+ // Return the date difference for the two times in a given timezone.
+ private static int dayDistance(android.icu.util.TimeZone icuTimeZone, long startTime,
+ long endTime) {
+ return julianDay(icuTimeZone, endTime) - julianDay(icuTimeZone, startTime);
+ }
+
+ private static int julianDay(android.icu.util.TimeZone icuTimeZone, long time) {
+ long utcMs = time + icuTimeZone.getOffset(time);
+ return (int) (utcMs / DAY_IN_MS) + EPOCH_JULIAN_DAY;
+ }
+}
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
index 825077ff..ad43f95 100644
--- a/core/java/android/view/ImeFocusController.java
+++ b/core/java/android/view/ImeFocusController.java
@@ -125,11 +125,11 @@
final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
onViewFocusChanged(viewForWindowFocus, true);
- // Skip starting input when the next focused view is same as served view and the served
- // input connection still exists.
+ // Starting new input when the next focused view is same as served view but the
+ // editor is not aligned with the same editor or editor is inactive.
final boolean nextFocusIsServedView = mServedView != null && mServedView == focusedView;
- if (nextFocusIsServedView && immDelegate.isAcceptingText()) {
- forceFocus = false;
+ if (nextFocusIsServedView && !immDelegate.isSameEditorAndAcceptingText(focusedView)) {
+ forceFocus = true;
}
immDelegate.startInputAsyncOnWindowFocusGain(viewForWindowFocus,
@@ -254,7 +254,7 @@
void setCurrentRootView(ViewRootImpl rootView);
boolean isCurrentRootView(ViewRootImpl rootView);
boolean isRestartOnNextWindowFocus(boolean reset);
- boolean isAcceptingText();
+ boolean isSameEditorAndAcceptingText(View view);
}
public View getServedView() {
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index c6be91f..a679b37 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -737,7 +737,7 @@
}
}
- final boolean hasControl = mTmpControlArray.size() > 0;
+ boolean requestedStateStale = false;
final int[] showTypes = new int[1];
final int[] hideTypes = new int[1];
@@ -754,9 +754,26 @@
// Ensure to create source consumers if not available yet.
for (int i = mTmpControlArray.size() - 1; i >= 0; i--) {
final InsetsSourceControl control = mTmpControlArray.valueAt(i);
- InsetsSourceConsumer consumer = getSourceConsumer(control.getType());
+ final @InternalInsetsType int type = control.getType();
+ final InsetsSourceConsumer consumer = getSourceConsumer(type);
consumer.setControl(control, showTypes, hideTypes);
+ if (!requestedStateStale) {
+ final boolean requestedVisible = consumer.isRequestedVisible();
+
+ // We might have changed our requested visibilities while we don't have the control,
+ // so we need to update our requested state once we have control. Otherwise, our
+ // requested state at the server side might be incorrect.
+ final boolean requestedVisibilityChanged =
+ requestedVisible != mRequestedState.getSourceOrDefaultVisibility(type);
+
+ // The IME client visibility will be reset by insets source provider while updating
+ // control, so if IME is requested visible, we need to send the request to server.
+ final boolean imeRequestedVisible = type == ITYPE_IME && requestedVisible;
+
+ requestedStateStale = requestedVisibilityChanged || imeRequestedVisible;
+ }
+
}
mTmpControlArray.clear();
@@ -772,10 +789,7 @@
if (hideTypes[0] != 0) {
applyAnimation(hideTypes[0], false /* show */, false /* fromIme */);
}
- if (hasControl && mRequestedState.hasSources()) {
- // We might have changed our requested visibilities while we don't have the control,
- // so we need to update our requested state once we have control. Otherwise, our
- // requested state at the server side might be incorrect.
+ if (requestedStateStale) {
updateRequestedState();
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b022d2a..f2cec25 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1816,13 +1816,19 @@
/**
* Called after window layout to update the bounds surface. If the surface insets have changed
* or the surface has resized, update the bounds surface.
+ *
+ * @param shouldReparent Whether it should reparent the bounds layer to the main SurfaceControl.
*/
- private void updateBoundsLayer() {
+ private void updateBoundsLayer(boolean shouldReparent) {
if (mBoundsLayer != null) {
setBoundsLayerCrop();
- mTransaction.deferTransactionUntil(mBoundsLayer,
- getRenderSurfaceControl(), mSurface.getNextFrameNumber())
- .apply();
+ mTransaction.deferTransactionUntil(mBoundsLayer, getRenderSurfaceControl(),
+ mSurface.getNextFrameNumber());
+
+ if (shouldReparent) {
+ mTransaction.reparent(mBoundsLayer, getRenderSurfaceControl());
+ }
+ mTransaction.apply();
}
}
@@ -2904,7 +2910,16 @@
}
if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
- updateBoundsLayer();
+ // If the surface has been replaced, there's a chance the bounds layer is not parented
+ // to the new layer. When updating bounds layer, also reparent to the main VRI
+ // SurfaceControl to ensure it's correctly placed in the hierarchy.
+ //
+ // This needs to be done on the client side since WMS won't reparent the children to the
+ // new surface if it thinks the app is closing. WMS gets the signal that the app is
+ // stopping, but on the client side it doesn't get stopped since it's restarted quick
+ // enough. WMS doesn't want to keep around old children since they will leak when the
+ // client creates new children.
+ updateBoundsLayer(surfaceReplaced);
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index fa92e29c..599a7c2 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -633,20 +633,21 @@
// we'll just do a window focus gain and call it a day.
try {
View servedView = controller.getServedView();
- boolean nextFocusIsServedView = servedView != null && servedView == focusedView;
+ boolean nextFocusSameEditor = servedView != null && servedView == focusedView
+ && isSameEditorAndAcceptingText(focusedView);
if (DEBUG) {
Log.v(TAG, "Reporting focus gain, without startInput"
- + ", nextFocusIsServedView=" + nextFocusIsServedView);
+ + ", nextFocusIsServedView=" + nextFocusSameEditor);
}
final int startInputReason =
- nextFocusIsServedView ? WINDOW_FOCUS_GAIN_REPORT_WITH_SAME_EDITOR
+ nextFocusSameEditor ? WINDOW_FOCUS_GAIN_REPORT_WITH_SAME_EDITOR
: WINDOW_FOCUS_GAIN_REPORT_WITHOUT_EDITOR;
mService.startInputOrWindowGainedFocus(
startInputReason, mClient,
focusedView.getWindowToken(), startInputFlags, softInputMode,
windowFlags,
- nextFocusIsServedView ? mCurrentTextBoxAttribute : null,
- nextFocusIsServedView ? mServedInputConnectionWrapper : null,
+ null,
+ null,
0 /* missingMethodFlags */,
mCurRootView.mContext.getApplicationInfo().targetSdkVersion);
} catch (RemoteException e) {
@@ -671,10 +672,6 @@
@Override
public void setCurrentRootView(ViewRootImpl rootView) {
synchronized (mH) {
- if (mCurRootView != null) {
- // Restart the input when the next window focus state of the root view changed.
- mRestartOnNextWindowFocus = true;
- }
mCurRootView = rootView;
}
}
@@ -704,14 +701,33 @@
}
/**
- * For {@link ImeFocusController} to check if the currently served view is accepting full
- * text edits.
+ * For {@link ImeFocusController} to check if the given focused view aligns with the same
+ * editor and the editor is active to accept the text input.
+ *
+ * TODO(b/160968797): Remove this method and move mCurrentTextBoxAttritube to
+ * ImeFocusController.
+ * In the long-term, we should make mCurrentTextBoxAtrtribue as per-window base instance,
+ * so that we we can directly check if the current focused view aligned with the same editor
+ * in the window without using this checking.
+ *
+ * Note that this method is only use for fixing start new input may ignored issue
+ * (e.g. b/160391516), DO NOT leverage this method to do another check.
*/
- @Override
- public boolean isAcceptingText() {
+ public boolean isSameEditorAndAcceptingText(View view) {
synchronized (mH) {
- return mServedInputConnectionWrapper != null
- && mServedInputConnectionWrapper.getInputConnection() != null;
+ if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null) {
+ return false;
+ }
+
+ final EditorInfo ic = mCurrentTextBoxAttribute;
+ // This sameEditor checking is based on using object hash comparison to check if
+ // some fields of the current EditorInfo (e.g. autoFillId, OpPackageName) the
+ // hash code is same as the given focused view.
+ final boolean sameEditor = view.onCheckIsTextEditor() && view.getId() == ic.fieldId
+ && view.getAutofillId() == ic.autofillId
+ && view.getContext().getOpPackageName() == ic.packageName;
+ return sameEditor && mServedInputConnectionWrapper != null
+ && mServedInputConnectionWrapper.isActive();
}
}
}
diff --git a/core/java/android/widget/DatePickerSpinnerDelegate.java b/core/java/android/widget/DatePickerSpinnerDelegate.java
index 096e6ea..fd89b2e 100644
--- a/core/java/android/widget/DatePickerSpinnerDelegate.java
+++ b/core/java/android/widget/DatePickerSpinnerDelegate.java
@@ -34,8 +34,6 @@
import android.widget.DatePicker.AbstractDatePickerDelegate;
import android.widget.NumberPicker.OnValueChangeListener;
-import libcore.icu.ICU;
-
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@@ -459,7 +457,7 @@
// We use numeric spinners for year and day, but textual months. Ask icu4c what
// order the user's locale uses for that combination. http://b/7207103.
String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd");
- char[] order = ICU.getDateFormatOrder(pattern);
+ char[] order = DateFormat.getDateFormatOrder(pattern);
final int spinnerCount = order.length;
for (int i = 0; i < spinnerCount; i++) {
switch (order[i]) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 14cf258..fbf050a 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2640,7 +2640,10 @@
}
RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
- if (gridAdapter == null || recyclerView == null) {
+ // Skip height calculation if recycler view was scrolled to prevent it inaccurately
+ // calculating the height, as the logic below does not account for the scrolled offset.
+ if (gridAdapter == null || recyclerView == null
+ || recyclerView.computeVerticalScrollOffset() != 0) {
return;
}
@@ -3127,6 +3130,10 @@
ChooserGridAdapter currentRootAdapter =
mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
currentRootAdapter.updateDirectShareExpansion();
+ // This fixes an edge case where after performing a variety of gestures, vertical scrolling
+ // ends up disabled. That's because at some point the old tab's vertical scrolling is
+ // disabled and the new tab's is enabled. For context, see b/159997845
+ setVerticalScrollEnabled(true);
}
@Override
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index ffa6041..3a65a32 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -252,8 +252,10 @@
@Override
protected void setupContainerPadding(View container) {
+ int initialBottomPadding = getContext().getResources().getDimensionPixelSize(
+ R.dimen.resolver_empty_state_container_padding_bottom);
container.setPadding(container.getPaddingLeft(), container.getPaddingTop(),
- container.getPaddingRight(), container.getPaddingBottom() + mBottomOffset);
+ container.getPaddingRight(), initialBottomPadding + mBottomOffset);
}
class ChooserProfileDescriptor extends ProfileDescriptor {
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index eef722e..c16ee42 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -833,7 +833,7 @@
String getAppSubLabelInternal() {
// Will default to app name if no intent filter or activity label set, make sure to
// check if subLabel matches label before final display
- return (String) mRi.loadLabel(mPm);
+ return mRi.loadLabel(mPm).toString();
}
}
diff --git a/core/java/com/android/internal/os/KernelWakelockReader.java b/core/java/com/android/internal/os/KernelWakelockReader.java
index cffb0ad..3d35d2f 100644
--- a/core/java/com/android/internal/os/KernelWakelockReader.java
+++ b/core/java/com/android/internal/os/KernelWakelockReader.java
@@ -153,19 +153,32 @@
}
/**
+ * Attempt to wait for suspend_control service if not immediately available.
+ */
+ private ISuspendControlService waitForSuspendControlService() throws ServiceNotFoundException {
+ final String name = "suspend_control";
+ final int numRetries = 5;
+ for (int i = 0; i < numRetries; i++) {
+ mSuspendControlService = ISuspendControlService.Stub.asInterface(
+ ServiceManager.getService(name));
+ if (mSuspendControlService != null) {
+ return mSuspendControlService;
+ }
+ }
+ throw new ServiceNotFoundException(name);
+ }
+
+ /**
* On success, returns the updated stats from SystemSupend, else returns null.
*/
private KernelWakelockStats getWakelockStatsFromSystemSuspend(
final KernelWakelockStats staleStats) {
WakeLockInfo[] wlStats = null;
- if (mSuspendControlService == null) {
- try {
- mSuspendControlService = ISuspendControlService.Stub.asInterface(
- ServiceManager.getServiceOrThrow("suspend_control"));
- } catch (ServiceNotFoundException e) {
- Slog.wtf(TAG, "Required service suspend_control not available", e);
- return null;
- }
+ try {
+ mSuspendControlService = waitForSuspendControlService();
+ } catch (ServiceNotFoundException e) {
+ Slog.wtf(TAG, "Required service suspend_control not available", e);
+ return null;
}
try {
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index 0c24065..7a79cc9 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -2088,10 +2088,11 @@
pw.println(getName() + ":");
pw.println(" total records=" + getLogRecCount());
for (int i = 0; i < getLogRecSize(); i++) {
- pw.println(" rec[" + i + "]: " + getLogRec(i).toString());
+ pw.println(" rec[" + i + "]: " + getLogRec(i));
pw.flush();
}
- pw.println("curState=" + getCurrentState().getName());
+ final IState curState = getCurrentState();
+ pw.println("curState=" + (curState == null ? "<QUIT>" : curState.getName()));
}
@Override
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 9e2e85a..b4b58ff 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -155,9 +155,8 @@
"android_media_ToneGenerator.cpp",
"android_hardware_Camera.cpp",
"android_hardware_camera2_CameraMetadata.cpp",
- "android_hardware_camera2_legacy_LegacyCameraDevice.cpp",
- "android_hardware_camera2_legacy_PerfMeasurement.cpp",
"android_hardware_camera2_DngCreator.cpp",
+ "android_hardware_camera2_utils_SurfaceUtils.cpp",
"android_hardware_display_DisplayManagerGlobal.cpp",
"android_hardware_display_DisplayViewport.cpp",
"android_hardware_HardwareBuffer.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 7b708ef..5b1196d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -73,9 +73,8 @@
extern int register_android_hardware_Camera(JNIEnv *env);
extern int register_android_hardware_camera2_CameraMetadata(JNIEnv *env);
-extern int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv *env);
-extern int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv *env);
extern int register_android_hardware_camera2_DngCreator(JNIEnv *env);
+extern int register_android_hardware_camera2_utils_SurfaceUtils(JNIEnv* env);
extern int register_android_hardware_display_DisplayManagerGlobal(JNIEnv* env);
extern int register_android_hardware_HardwareBuffer(JNIEnv *env);
extern int register_android_hardware_SensorManager(JNIEnv *env);
@@ -1526,9 +1525,8 @@
REG_JNI(register_com_android_internal_util_VirtualRefBasePtr),
REG_JNI(register_android_hardware_Camera),
REG_JNI(register_android_hardware_camera2_CameraMetadata),
- REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice),
- REG_JNI(register_android_hardware_camera2_legacy_PerfMeasurement),
REG_JNI(register_android_hardware_camera2_DngCreator),
+ REG_JNI(register_android_hardware_camera2_utils_SurfaceUtils),
REG_JNI(register_android_hardware_display_DisplayManagerGlobal),
REG_JNI(register_android_hardware_HardwareBuffer),
REG_JNI(register_android_hardware_SensorManager),
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index bc69735..e47f18a 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -556,7 +556,7 @@
// connect to camera service
static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
- jobject weak_this, jint cameraId, jint halVersion, jstring clientPackageName)
+ jobject weak_this, jint cameraId, jstring clientPackageName)
{
// Convert jstring to String16
const char16_t *rawClientName = reinterpret_cast<const char16_t*>(
@@ -566,19 +566,8 @@
env->ReleaseStringChars(clientPackageName,
reinterpret_cast<const jchar*>(rawClientName));
- sp<Camera> camera;
- if (halVersion == CAMERA_HAL_API_VERSION_NORMAL_CONNECT) {
- // Default path: hal version is don't care, do normal camera connect.
- camera = Camera::connect(cameraId, clientName,
- Camera::USE_CALLING_UID, Camera::USE_CALLING_PID);
- } else {
- jint status = Camera::connectLegacy(cameraId, halVersion, clientName,
- Camera::USE_CALLING_UID, camera);
- if (status != NO_ERROR) {
- return status;
- }
- }
-
+ sp<Camera> camera =
+ Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID, Camera::USE_CALLING_PID);
if (camera == NULL) {
return -EACCES;
}
@@ -1068,7 +1057,7 @@
"(ILandroid/hardware/Camera$CameraInfo;)V",
(void*)android_hardware_Camera_getCameraInfo },
{ "native_setup",
- "(Ljava/lang/Object;IILjava/lang/String;)I",
+ "(Ljava/lang/Object;ILjava/lang/String;)I",
(void*)android_hardware_Camera_native_setup },
{ "native_release",
"()V",
diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
deleted file mode 100644
index 8cf1d2c..0000000
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ /dev/null
@@ -1,841 +0,0 @@
-/*
- * Copyright (C) 2014 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.
- */
-
-#define LOG_TAG "Legacy-CameraDevice-JNI"
-// #define LOG_NDEBUG 0
-#include <utils/Log.h>
-#include <utils/Errors.h>
-#include <utils/Trace.h>
-#include <camera/CameraUtils.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
-#include "android_runtime/android_view_Surface.h"
-#include "android_runtime/android_graphics_SurfaceTexture.h"
-
-#include <gui/IGraphicBufferProducer.h>
-#include <gui/IProducerListener.h>
-#include <gui/Surface.h>
-#include <hardware/camera3.h>
-#include <surfacetexture/SurfaceTexture.h>
-#include <system/camera_metadata.h>
-#include <system/window.h>
-#include <ui/GraphicBuffer.h>
-
-#include <stdint.h>
-#include <inttypes.h>
-
-using namespace android;
-
-// fully-qualified class name
-#define CAMERA_DEVICE_CLASS_NAME "android/hardware/camera2/legacy/LegacyCameraDevice"
-#define CAMERA_DEVICE_BUFFER_SLACK 3
-#define DONT_CARE 0
-
-#define ARRAY_SIZE(a) (sizeof(a)/sizeof(*(a)))
-
-#define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) )
-
-// Use BAD_VALUE for surface abandoned error
-#define OVERRIDE_SURFACE_ERROR(err) \
-do { \
- if (err == -ENODEV) { \
- err = BAD_VALUE; \
- } \
-} while (0)
-
-#define UPDATE(md, tag, data, size) \
-do { \
- if ((md).update((tag), (data), (size))) { \
- ALOGE("Update " #tag " failed!"); \
- return BAD_VALUE; \
- } \
-} while (0)
-
-/**
- * Convert from RGB 888 to Y'CbCr using the conversion specified in JFIF v1.02
- */
-static void rgbToYuv420(uint8_t* rgbBuf, size_t width, size_t height, uint8_t* yPlane,
- uint8_t* crPlane, uint8_t* cbPlane, size_t chromaStep, size_t yStride, size_t chromaStride) {
- uint8_t R, G, B;
- size_t index = 0;
- for (size_t j = 0; j < height; j++) {
- uint8_t* cr = crPlane;
- uint8_t* cb = cbPlane;
- uint8_t* y = yPlane;
- bool jEven = (j & 1) == 0;
- for (size_t i = 0; i < width; i++) {
- R = rgbBuf[index++];
- G = rgbBuf[index++];
- B = rgbBuf[index++];
- *y++ = (77 * R + 150 * G + 29 * B) >> 8;
- if (jEven && (i & 1) == 0) {
- *cb = (( -43 * R - 85 * G + 128 * B) >> 8) + 128;
- *cr = (( 128 * R - 107 * G - 21 * B) >> 8) + 128;
- cr += chromaStep;
- cb += chromaStep;
- }
- // Skip alpha
- index++;
- }
- yPlane += yStride;
- if (jEven) {
- crPlane += chromaStride;
- cbPlane += chromaStride;
- }
- }
-}
-
-static void rgbToYuv420(uint8_t* rgbBuf, size_t width, size_t height, android_ycbcr* ycbcr) {
- size_t cStep = ycbcr->chroma_step;
- size_t cStride = ycbcr->cstride;
- size_t yStride = ycbcr->ystride;
- ALOGV("%s: yStride is: %zu, cStride is: %zu, cStep is: %zu", __FUNCTION__, yStride, cStride,
- cStep);
- rgbToYuv420(rgbBuf, width, height, reinterpret_cast<uint8_t*>(ycbcr->y),
- reinterpret_cast<uint8_t*>(ycbcr->cr), reinterpret_cast<uint8_t*>(ycbcr->cb),
- cStep, yStride, cStride);
-}
-
-static status_t connectSurface(const sp<Surface>& surface, int32_t maxBufferSlack) {
- status_t err = NO_ERROR;
-
- err = surface->connect(NATIVE_WINDOW_API_CAMERA, /*listener*/NULL);
- if (err != OK) {
- ALOGE("%s: Unable to connect to surface, error %s (%d).", __FUNCTION__,
- strerror(-err), err);
- return err;
- }
-
- err = native_window_set_usage(surface.get(), GRALLOC_USAGE_SW_WRITE_OFTEN);
- if (err != NO_ERROR) {
- ALOGE("%s: Failed to set native window usage flag, error %s (%d).", __FUNCTION__,
- strerror(-err), err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
-
- int minUndequeuedBuffers;
- err = static_cast<ANativeWindow*>(surface.get())->query(surface.get(),
- NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &minUndequeuedBuffers);
- if (err != NO_ERROR) {
- ALOGE("%s: Failed to get native window min undequeued buffers, error %s (%d).",
- __FUNCTION__, strerror(-err), err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
-
- ALOGV("%s: Setting buffer count to %d", __FUNCTION__,
- maxBufferSlack + 1 + minUndequeuedBuffers);
- err = native_window_set_buffer_count(surface.get(), maxBufferSlack + 1 + minUndequeuedBuffers);
- if (err != NO_ERROR) {
- ALOGE("%s: Failed to set native window buffer count, error %s (%d).", __FUNCTION__,
- strerror(-err), err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
- return NO_ERROR;
-}
-
-/**
- * Produce a frame in the given surface.
- *
- * Args:
- * anw - a surface to produce a frame in.
- * pixelBuffer - image buffer to generate a frame from.
- * width - width of the pixelBuffer in pixels.
- * height - height of the pixelBuffer in pixels.
- * pixelFmt - format of the pixelBuffer, one of:
- * HAL_PIXEL_FORMAT_YCrCb_420_SP,
- * HAL_PIXEL_FORMAT_YCbCr_420_888,
- * HAL_PIXEL_FORMAT_BLOB
- * bufSize - the size of the pixelBuffer in bytes.
- */
-static status_t produceFrame(const sp<ANativeWindow>& anw,
- uint8_t* pixelBuffer,
- int32_t bufWidth, // Width of the pixelBuffer
- int32_t bufHeight, // Height of the pixelBuffer
- int32_t pixelFmt, // Format of the pixelBuffer
- int32_t bufSize) {
- ATRACE_CALL();
- status_t err = NO_ERROR;
- ANativeWindowBuffer* anb;
- ALOGV("%s: Dequeue buffer from %p %dx%d (fmt=%x, size=%x)",
- __FUNCTION__, anw.get(), bufWidth, bufHeight, pixelFmt, bufSize);
-
- if (anw == 0) {
- ALOGE("%s: anw must not be NULL", __FUNCTION__);
- return BAD_VALUE;
- } else if (pixelBuffer == NULL) {
- ALOGE("%s: pixelBuffer must not be NULL", __FUNCTION__);
- return BAD_VALUE;
- } else if (bufWidth < 0) {
- ALOGE("%s: width must be non-negative", __FUNCTION__);
- return BAD_VALUE;
- } else if (bufHeight < 0) {
- ALOGE("%s: height must be non-negative", __FUNCTION__);
- return BAD_VALUE;
- } else if (bufSize < 0) {
- ALOGE("%s: bufSize must be non-negative", __FUNCTION__);
- return BAD_VALUE;
- }
-
- size_t width = static_cast<size_t>(bufWidth);
- size_t height = static_cast<size_t>(bufHeight);
- size_t bufferLength = static_cast<size_t>(bufSize);
-
- // TODO: Switch to using Surface::lock and Surface::unlockAndPost
- err = native_window_dequeue_buffer_and_wait(anw.get(), &anb);
- if (err != NO_ERROR) {
- ALOGE("%s: Failed to dequeue buffer, error %s (%d).", __FUNCTION__,
- strerror(-err), err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
-
- sp<GraphicBuffer> buf(GraphicBuffer::from(anb));
- uint32_t grallocBufWidth = buf->getWidth();
- uint32_t grallocBufHeight = buf->getHeight();
- uint32_t grallocBufStride = buf->getStride();
- if (grallocBufWidth != width || grallocBufHeight != height) {
- ALOGE("%s: Received gralloc buffer with bad dimensions %" PRIu32 "x%" PRIu32
- ", expecting dimensions %zu x %zu", __FUNCTION__, grallocBufWidth,
- grallocBufHeight, width, height);
- return BAD_VALUE;
- }
-
- int32_t bufFmt = 0;
- err = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &bufFmt);
- if (err != NO_ERROR) {
- ALOGE("%s: Error while querying surface pixel format %s (%d).", __FUNCTION__,
- strerror(-err), err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
-
- uint64_t tmpSize = (pixelFmt == HAL_PIXEL_FORMAT_BLOB) ? grallocBufWidth :
- 4 * grallocBufHeight * grallocBufWidth;
- if (bufFmt != pixelFmt) {
- if (bufFmt == HAL_PIXEL_FORMAT_RGBA_8888 && pixelFmt == HAL_PIXEL_FORMAT_BLOB) {
- ALOGV("%s: Using BLOB to RGBA format override.", __FUNCTION__);
- tmpSize = 4 * (grallocBufWidth + grallocBufStride * (grallocBufHeight - 1));
- } else {
- ALOGW("%s: Format mismatch in produceFrame: expecting format %#" PRIx32
- ", but received buffer with format %#" PRIx32, __FUNCTION__, pixelFmt, bufFmt);
- }
- }
-
- if (tmpSize > SIZE_MAX) {
- ALOGE("%s: Overflow calculating size, buffer with dimens %zu x %zu is absurdly large...",
- __FUNCTION__, width, height);
- return BAD_VALUE;
- }
-
- size_t totalSizeBytes = tmpSize;
-
- ALOGV("%s: Pixel format chosen: %x", __FUNCTION__, pixelFmt);
- switch(pixelFmt) {
- case HAL_PIXEL_FORMAT_YCrCb_420_SP: {
- if (bufferLength < totalSizeBytes) {
- ALOGE("%s: PixelBuffer size %zu too small for given dimensions",
- __FUNCTION__, bufferLength);
- return BAD_VALUE;
- }
- uint8_t* img = NULL;
- ALOGV("%s: Lock buffer from %p for write", __FUNCTION__, anw.get());
- err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
- if (err != NO_ERROR) return err;
-
- uint8_t* yPlane = img;
- uint8_t* uPlane = img + height * width;
- uint8_t* vPlane = uPlane + 1;
- size_t chromaStep = 2;
- size_t yStride = width;
- size_t chromaStride = width;
-
- rgbToYuv420(pixelBuffer, width, height, yPlane,
- uPlane, vPlane, chromaStep, yStride, chromaStride);
- break;
- }
- case HAL_PIXEL_FORMAT_YV12: {
- if (bufferLength < totalSizeBytes) {
- ALOGE("%s: PixelBuffer size %zu too small for given dimensions",
- __FUNCTION__, bufferLength);
- return BAD_VALUE;
- }
-
- if ((width & 1) || (height & 1)) {
- ALOGE("%s: Dimens %zu x %zu are not divisible by 2.", __FUNCTION__, width, height);
- return BAD_VALUE;
- }
-
- uint8_t* img = NULL;
- ALOGV("%s: Lock buffer from %p for write", __FUNCTION__, anw.get());
- err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
- if (err != NO_ERROR) {
- ALOGE("%s: Error %s (%d) while locking gralloc buffer for write.", __FUNCTION__,
- strerror(-err), err);
- return err;
- }
-
- uint32_t stride = buf->getStride();
- ALOGV("%s: stride is: %" PRIu32, __FUNCTION__, stride);
- LOG_ALWAYS_FATAL_IF(stride % 16, "Stride is not 16 pixel aligned %d", stride);
-
- uint32_t cStride = ALIGN(stride / 2, 16);
- size_t chromaStep = 1;
-
- uint8_t* yPlane = img;
- uint8_t* crPlane = img + static_cast<uint32_t>(height) * stride;
- uint8_t* cbPlane = crPlane + cStride * static_cast<uint32_t>(height) / 2;
-
- rgbToYuv420(pixelBuffer, width, height, yPlane,
- crPlane, cbPlane, chromaStep, stride, cStride);
- break;
- }
- case HAL_PIXEL_FORMAT_YCbCr_420_888: {
- // Software writes with YCbCr_420_888 format are unsupported
- // by the gralloc module for now
- if (bufferLength < totalSizeBytes) {
- ALOGE("%s: PixelBuffer size %zu too small for given dimensions",
- __FUNCTION__, bufferLength);
- return BAD_VALUE;
- }
- android_ycbcr ycbcr = android_ycbcr();
- ALOGV("%s: Lock buffer from %p for write", __FUNCTION__, anw.get());
-
- err = buf->lockYCbCr(GRALLOC_USAGE_SW_WRITE_OFTEN, &ycbcr);
- if (err != NO_ERROR) {
- ALOGE("%s: Failed to lock ycbcr buffer, error %s (%d).", __FUNCTION__,
- strerror(-err), err);
- return err;
- }
- rgbToYuv420(pixelBuffer, width, height, &ycbcr);
- break;
- }
- case HAL_PIXEL_FORMAT_BLOB: {
- int8_t* img = NULL;
- struct camera3_jpeg_blob footer = {
- .jpeg_blob_id = CAMERA3_JPEG_BLOB_ID,
- .jpeg_size = (uint32_t)bufferLength
- };
-
- size_t totalJpegSize = bufferLength + sizeof(footer);
- totalJpegSize = (totalJpegSize + 3) & ~0x3; // round up to nearest octonibble
-
- if (totalJpegSize > totalSizeBytes) {
- ALOGE("%s: Pixel buffer needs size %zu, cannot fit in gralloc buffer of size %zu",
- __FUNCTION__, totalJpegSize, totalSizeBytes);
- return BAD_VALUE;
- }
-
- err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
- if (err != NO_ERROR) {
- ALOGE("%s: Failed to lock buffer, error %s (%d).", __FUNCTION__, strerror(-err),
- err);
- return err;
- }
-
- memcpy(img, pixelBuffer, bufferLength);
- memcpy(img + totalSizeBytes - sizeof(footer), &footer, sizeof(footer));
- break;
- }
- default: {
- ALOGE("%s: Invalid pixel format in produceFrame: %x", __FUNCTION__, pixelFmt);
- return BAD_VALUE;
- }
- }
-
- ALOGV("%s: Unlock buffer from %p", __FUNCTION__, anw.get());
- err = buf->unlock();
- if (err != NO_ERROR) {
- ALOGE("%s: Failed to unlock buffer, error %s (%d).", __FUNCTION__, strerror(-err), err);
- return err;
- }
-
- ALOGV("%s: Queue buffer to %p", __FUNCTION__, anw.get());
- err = anw->queueBuffer(anw.get(), buf->getNativeBuffer(), /*fenceFd*/-1);
- if (err != NO_ERROR) {
- ALOGE("%s: Failed to queue buffer, error %s (%d).", __FUNCTION__, strerror(-err), err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
- return NO_ERROR;
-}
-
-static sp<ANativeWindow> getNativeWindow(JNIEnv* env, jobject surface) {
- sp<ANativeWindow> anw;
- if (surface) {
- anw = android_view_Surface_getNativeWindow(env, surface);
- if (env->ExceptionCheck()) {
- return NULL;
- }
- } else {
- jniThrowNullPointerException(env, "surface");
- return NULL;
- }
- if (anw == NULL) {
- ALOGE("%s: Surface had no valid native window.", __FUNCTION__);
- return NULL;
- }
- return anw;
-}
-
-static sp<ANativeWindow> getSurfaceTextureNativeWindow(JNIEnv* env, jobject thiz) {
- sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, thiz));
- sp<Surface> surfaceTextureClient(producer != NULL ? new Surface(producer) : NULL);
- return surfaceTextureClient;
-}
-
-static sp<ANativeWindow> getNativeWindowFromTexture(JNIEnv* env, jobject surfaceTexture) {
- sp<ANativeWindow> anw;
- if (surfaceTexture) {
- anw = getSurfaceTextureNativeWindow(env, surfaceTexture);
- if (env->ExceptionCheck()) {
- return NULL;
- }
- } else {
- jniThrowNullPointerException(env, "surfaceTexture");
- return NULL;
- }
- if (anw == NULL) {
- jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
- "SurfaceTexture had no valid native window.");
- return NULL;
- }
- return anw;
-}
-
-static sp<Surface> getSurface(JNIEnv* env, jobject surface) {
- sp<Surface> s;
- if (surface) {
- s = android_view_Surface_getSurface(env, surface);
- if (env->ExceptionCheck()) {
- return NULL;
- }
- } else {
- jniThrowNullPointerException(env, "surface");
- return NULL;
- }
- if (s == NULL) {
- jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
- "Surface had no valid native Surface.");
- return NULL;
- }
- return s;
-}
-
-extern "C" {
-
-static jint LegacyCameraDevice_nativeDetectSurfaceType(JNIEnv* env, jobject thiz, jobject surface) {
- ALOGV("nativeDetectSurfaceType");
- sp<ANativeWindow> anw;
- if ((anw = getNativeWindow(env, surface)) == NULL) {
- ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return BAD_VALUE;
- }
- int32_t fmt = 0;
- status_t err = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &fmt);
- if(err != NO_ERROR) {
- ALOGE("%s: Error while querying surface pixel format %s (%d).", __FUNCTION__, strerror(-err),
- err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
- return fmt;
-}
-
-static jint LegacyCameraDevice_nativeDetectSurfaceDataspace(JNIEnv* env, jobject thiz, jobject surface) {
- ALOGV("nativeDetectSurfaceDataspace");
- sp<ANativeWindow> anw;
- if ((anw = getNativeWindow(env, surface)) == NULL) {
- ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return BAD_VALUE;
- }
- int32_t fmt = 0;
- status_t err = anw->query(anw.get(), NATIVE_WINDOW_DEFAULT_DATASPACE, &fmt);
- if(err != NO_ERROR) {
- ALOGE("%s: Error while querying surface dataspace %s (%d).", __FUNCTION__, strerror(-err),
- err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
- return fmt;
-}
-
-static jint LegacyCameraDevice_nativeDetectSurfaceDimens(JNIEnv* env, jobject thiz,
- jobject surface, jintArray dimens) {
- ALOGV("nativeGetSurfaceDimens");
-
- if (dimens == NULL) {
- ALOGE("%s: Null dimens argument passed to nativeDetectSurfaceDimens", __FUNCTION__);
- return BAD_VALUE;
- }
-
- if (env->GetArrayLength(dimens) < 2) {
- ALOGE("%s: Invalid length of dimens argument in nativeDetectSurfaceDimens", __FUNCTION__);
- return BAD_VALUE;
- }
-
- sp<ANativeWindow> anw;
- if ((anw = getNativeWindow(env, surface)) == NULL) {
- ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return BAD_VALUE;
- }
- int32_t dimenBuf[2];
- status_t err = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, dimenBuf);
- if(err != NO_ERROR) {
- ALOGE("%s: Error while querying surface width %s (%d).", __FUNCTION__, strerror(-err),
- err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
- err = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, dimenBuf + 1);
- if(err != NO_ERROR) {
- ALOGE("%s: Error while querying surface height %s (%d).", __FUNCTION__, strerror(-err),
- err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
- env->SetIntArrayRegion(dimens, /*start*/0, /*length*/ARRAY_SIZE(dimenBuf), dimenBuf);
- return NO_ERROR;
-}
-
-static jint LegacyCameraDevice_nativeDetectSurfaceUsageFlags(JNIEnv* env, jobject thiz,
- jobject surface) {
- ALOGV("nativeDetectSurfaceUsageFlags");
-
- sp<ANativeWindow> anw;
- if ((anw = getNativeWindow(env, surface)) == NULL) {
- jniThrowException(env, "java/lang/UnsupportedOperationException",
- "Could not retrieve native window from surface.");
- return BAD_VALUE;
- }
- int32_t usage = 0;
- status_t err = anw->query(anw.get(), NATIVE_WINDOW_CONSUMER_USAGE_BITS, &usage);
- if(err != NO_ERROR) {
- jniThrowException(env, "java/lang/UnsupportedOperationException",
- "Error while querying surface usage bits");
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
- return usage;
-}
-
-static jint LegacyCameraDevice_nativeDisconnectSurface(JNIEnv* env, jobject thiz,
- jobject surface) {
- ALOGV("nativeDisconnectSurface");
- if (surface == nullptr) return NO_ERROR;
-
- sp<ANativeWindow> anw;
- if ((anw = getNativeWindow(env, surface)) == NULL) {
- ALOGV("Buffer queue has already been abandoned.");
- return NO_ERROR;
- }
-
- status_t err = native_window_api_disconnect(anw.get(), NATIVE_WINDOW_API_CAMERA);
- if(err != NO_ERROR) {
- jniThrowException(env, "java/lang/UnsupportedOperationException",
- "Error while disconnecting surface");
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
- return NO_ERROR;
-}
-
-static jint LegacyCameraDevice_nativeDetectTextureDimens(JNIEnv* env, jobject thiz,
- jobject surfaceTexture, jintArray dimens) {
- ALOGV("nativeDetectTextureDimens");
- sp<ANativeWindow> anw;
- if ((anw = getNativeWindowFromTexture(env, surfaceTexture)) == NULL) {
- ALOGE("%s: Could not retrieve native window from SurfaceTexture.", __FUNCTION__);
- return BAD_VALUE;
- }
-
- int32_t dimenBuf[2];
- status_t err = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, dimenBuf);
- if(err != NO_ERROR) {
- ALOGE("%s: Error while querying SurfaceTexture width %s (%d)", __FUNCTION__,
- strerror(-err), err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
-
- err = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, dimenBuf + 1);
- if(err != NO_ERROR) {
- ALOGE("%s: Error while querying SurfaceTexture height %s (%d)", __FUNCTION__,
- strerror(-err), err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
-
- env->SetIntArrayRegion(dimens, /*start*/0, /*length*/ARRAY_SIZE(dimenBuf), dimenBuf);
- if (env->ExceptionCheck()) {
- return BAD_VALUE;
- }
- return NO_ERROR;
-}
-
-static jint LegacyCameraDevice_nativeConnectSurface(JNIEnv* env, jobject thiz, jobject surface) {
- ALOGV("nativeConnectSurface");
- sp<Surface> s;
- if ((s = getSurface(env, surface)) == NULL) {
- ALOGE("%s: Could not retrieve surface.", __FUNCTION__);
- return BAD_VALUE;
- }
- status_t err = connectSurface(s, CAMERA_DEVICE_BUFFER_SLACK);
- if (err != NO_ERROR) {
- ALOGE("%s: Error while configuring surface %s (%d).", __FUNCTION__, strerror(-err), err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
- return NO_ERROR;
-}
-
-static jint LegacyCameraDevice_nativeProduceFrame(JNIEnv* env, jobject thiz, jobject surface,
- jbyteArray pixelBuffer, jint width, jint height, jint pixelFormat) {
- ALOGV("nativeProduceFrame");
- sp<ANativeWindow> anw;
-
- if ((anw = getNativeWindow(env, surface)) == NULL) {
- ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return BAD_VALUE;
- }
-
- if (pixelBuffer == NULL) {
- jniThrowNullPointerException(env, "pixelBuffer");
- return DONT_CARE;
- }
-
- int32_t bufSize = static_cast<int32_t>(env->GetArrayLength(pixelBuffer));
- jbyte* pixels = env->GetByteArrayElements(pixelBuffer, /*is_copy*/NULL);
-
- if (pixels == NULL) {
- jniThrowNullPointerException(env, "pixels");
- return DONT_CARE;
- }
-
- status_t err = produceFrame(anw, reinterpret_cast<uint8_t*>(pixels), width, height,
- pixelFormat, bufSize);
- env->ReleaseByteArrayElements(pixelBuffer, pixels, JNI_ABORT);
-
- if (err != NO_ERROR) {
- ALOGE("%s: Error while producing frame %s (%d).", __FUNCTION__, strerror(-err), err);
- return err;
- }
- return NO_ERROR;
-}
-
-static jint LegacyCameraDevice_nativeSetSurfaceFormat(JNIEnv* env, jobject thiz, jobject surface,
- jint pixelFormat) {
- ALOGV("nativeSetSurfaceType");
- sp<ANativeWindow> anw;
- if ((anw = getNativeWindow(env, surface)) == NULL) {
- ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return BAD_VALUE;
- }
- status_t err = native_window_set_buffers_format(anw.get(), pixelFormat);
- if (err != NO_ERROR) {
- ALOGE("%s: Error while setting surface format %s (%d).", __FUNCTION__, strerror(-err), err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
- return NO_ERROR;
-}
-
-static jint LegacyCameraDevice_nativeSetSurfaceDimens(JNIEnv* env, jobject thiz, jobject surface,
- jint width, jint height) {
- ALOGV("nativeSetSurfaceDimens");
- sp<ANativeWindow> anw;
- if ((anw = getNativeWindow(env, surface)) == NULL) {
- ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return BAD_VALUE;
- }
-
- // Set user dimensions only
- // The producer dimensions are owned by GL
- status_t err = native_window_set_buffers_user_dimensions(anw.get(), width, height);
- if (err != NO_ERROR) {
- ALOGE("%s: Error while setting surface user dimens %s (%d).", __FUNCTION__, strerror(-err),
- err);
- OVERRIDE_SURFACE_ERROR(err);
- return err;
- }
- return NO_ERROR;
-}
-
-static jlong LegacyCameraDevice_nativeGetSurfaceId(JNIEnv* env, jobject thiz, jobject surface) {
- ALOGV("nativeGetSurfaceId");
- sp<Surface> s;
- if ((s = getSurface(env, surface)) == NULL) {
- ALOGE("%s: Could not retrieve native Surface from surface.", __FUNCTION__);
- return 0;
- }
- sp<IGraphicBufferProducer> gbp = s->getIGraphicBufferProducer();
- if (gbp == NULL) {
- ALOGE("%s: Could not retrieve IGraphicBufferProducer from surface.", __FUNCTION__);
- return 0;
- }
- sp<IBinder> b = IInterface::asBinder(gbp);
- if (b == NULL) {
- ALOGE("%s: Could not retrieve IBinder from surface.", __FUNCTION__);
- return 0;
- }
- /*
- * FIXME: Use better unique ID for surfaces than native IBinder pointer. Fix also in the camera
- * service (CameraDeviceClient.h).
- */
- return reinterpret_cast<jlong>(b.get());
-}
-
-static jint LegacyCameraDevice_nativeSetSurfaceOrientation(JNIEnv* env, jobject thiz,
- jobject surface, jint facing, jint orientation) {
- ALOGV("nativeSetSurfaceOrientation");
- sp<ANativeWindow> anw;
- if ((anw = getNativeWindow(env, surface)) == NULL) {
- ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return BAD_VALUE;
- }
-
- status_t err = NO_ERROR;
- CameraMetadata staticMetadata;
-
- int32_t orientVal = static_cast<int32_t>(orientation);
- uint8_t facingVal = static_cast<uint8_t>(facing);
- staticMetadata.update(ANDROID_SENSOR_ORIENTATION, &orientVal, 1);
- staticMetadata.update(ANDROID_LENS_FACING, &facingVal, 1);
-
- int32_t transform = 0;
-
- if ((err = CameraUtils::getRotationTransform(staticMetadata, /*out*/&transform)) != NO_ERROR) {
- ALOGE("%s: Invalid rotation transform %s (%d)", __FUNCTION__, strerror(-err),
- err);
- return err;
- }
-
- ALOGV("%s: Setting buffer sticky transform to %d", __FUNCTION__, transform);
-
- if ((err = native_window_set_buffers_sticky_transform(anw.get(), transform)) != NO_ERROR) {
- ALOGE("%s: Unable to configure surface transform, error %s (%d)", __FUNCTION__,
- strerror(-err), err);
- return err;
- }
-
- return NO_ERROR;
-}
-
-static jint LegacyCameraDevice_nativeSetNextTimestamp(JNIEnv* env, jobject thiz, jobject surface,
- jlong timestamp) {
- ALOGV("nativeSetNextTimestamp");
- sp<ANativeWindow> anw;
- if ((anw = getNativeWindow(env, surface)) == NULL) {
- ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return BAD_VALUE;
- }
-
- status_t err = NO_ERROR;
-
- if ((err = native_window_set_buffers_timestamp(anw.get(), static_cast<int64_t>(timestamp))) !=
- NO_ERROR) {
- ALOGE("%s: Unable to set surface timestamp, error %s (%d)", __FUNCTION__, strerror(-err),
- err);
- return err;
- }
- return NO_ERROR;
-}
-
-static jint LegacyCameraDevice_nativeSetScalingMode(JNIEnv* env, jobject thiz, jobject surface,
- jint mode) {
- ALOGV("nativeSetScalingMode");
- sp<ANativeWindow> anw;
- if ((anw = getNativeWindow(env, surface)) == NULL) {
- ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return BAD_VALUE;
- }
- status_t err = NO_ERROR;
- if ((err = native_window_set_scaling_mode(anw.get(), static_cast<int>(mode))) != NO_ERROR) {
- ALOGE("%s: Unable to set surface scaling mode, error %s (%d)", __FUNCTION__,
- strerror(-err), err);
- return err;
- }
- return NO_ERROR;
-}
-
-static jint LegacyCameraDevice_nativeGetJpegFooterSize(JNIEnv* env, jobject thiz) {
- ALOGV("nativeGetJpegFooterSize");
- return static_cast<jint>(sizeof(struct camera3_jpeg_blob));
-}
-
-} // extern "C"
-
-static const JNINativeMethod gCameraDeviceMethods[] = {
- { "nativeDetectSurfaceType",
- "(Landroid/view/Surface;)I",
- (void *)LegacyCameraDevice_nativeDetectSurfaceType },
- { "nativeDetectSurfaceDataspace",
- "(Landroid/view/Surface;)I",
- (void *)LegacyCameraDevice_nativeDetectSurfaceDataspace },
- { "nativeDetectSurfaceDimens",
- "(Landroid/view/Surface;[I)I",
- (void *)LegacyCameraDevice_nativeDetectSurfaceDimens },
- { "nativeConnectSurface",
- "(Landroid/view/Surface;)I",
- (void *)LegacyCameraDevice_nativeConnectSurface },
- { "nativeProduceFrame",
- "(Landroid/view/Surface;[BIII)I",
- (void *)LegacyCameraDevice_nativeProduceFrame },
- { "nativeSetSurfaceFormat",
- "(Landroid/view/Surface;I)I",
- (void *)LegacyCameraDevice_nativeSetSurfaceFormat },
- { "nativeSetSurfaceDimens",
- "(Landroid/view/Surface;II)I",
- (void *)LegacyCameraDevice_nativeSetSurfaceDimens },
- { "nativeGetSurfaceId",
- "(Landroid/view/Surface;)J",
- (void *)LegacyCameraDevice_nativeGetSurfaceId },
- { "nativeDetectTextureDimens",
- "(Landroid/graphics/SurfaceTexture;[I)I",
- (void *)LegacyCameraDevice_nativeDetectTextureDimens },
- { "nativeSetSurfaceOrientation",
- "(Landroid/view/Surface;II)I",
- (void *)LegacyCameraDevice_nativeSetSurfaceOrientation },
- { "nativeSetNextTimestamp",
- "(Landroid/view/Surface;J)I",
- (void *)LegacyCameraDevice_nativeSetNextTimestamp },
- { "nativeGetJpegFooterSize",
- "()I",
- (void *)LegacyCameraDevice_nativeGetJpegFooterSize },
- { "nativeDetectSurfaceUsageFlags",
- "(Landroid/view/Surface;)I",
- (void *)LegacyCameraDevice_nativeDetectSurfaceUsageFlags },
- { "nativeSetScalingMode",
- "(Landroid/view/Surface;I)I",
- (void *)LegacyCameraDevice_nativeSetScalingMode },
- { "nativeDisconnectSurface",
- "(Landroid/view/Surface;)I",
- (void *)LegacyCameraDevice_nativeDisconnectSurface },
-};
-
-// Get all the required offsets in java class and register native functions
-int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv* env)
-{
- // Register native functions
- return RegisterMethodsOrDie(env,
- CAMERA_DEVICE_CLASS_NAME,
- gCameraDeviceMethods,
- NELEM(gCameraDeviceMethods));
-}
diff --git a/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp b/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp
deleted file mode 100644
index fac243a..0000000
--- a/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright (C) 2014 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.
- */
-
-#define LOG_TAG "Camera2-Legacy-PerfMeasurement-JNI"
-#include <utils/Log.h>
-#include <utils/Errors.h>
-#include <utils/Trace.h>
-#include <utils/Vector.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
-
-#include <ui/GraphicBuffer.h>
-#include <system/window.h>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-
-using namespace android;
-
-// fully-qualified class name
-#define PERF_MEASUREMENT_CLASS_NAME "android/hardware/camera2/legacy/PerfMeasurement"
-
-/** GL utility methods copied from com_google_android_gles_jni_GLImpl.cpp */
-
-// Check if the extension at the head of pExtensions is pExtension. Note that pExtensions is
-// terminated by either 0 or space, while pExtension is terminated by 0.
-
-static bool
-extensionEqual(const GLubyte* pExtensions, const GLubyte* pExtension) {
- while (true) {
- char a = *pExtensions++;
- char b = *pExtension++;
- bool aEnd = a == '\0' || a == ' ';
- bool bEnd = b == '\0';
- if (aEnd || bEnd) {
- return aEnd == bEnd;
- }
- if (a != b) {
- return false;
- }
- }
-}
-
-static const GLubyte*
-nextExtension(const GLubyte* pExtensions) {
- while (true) {
- char a = *pExtensions++;
- if (a == '\0') {
- return pExtensions-1;
- } else if ( a == ' ') {
- return pExtensions;
- }
- }
-}
-
-static bool
-checkForExtension(const GLubyte* pExtensions, const GLubyte* pExtension) {
- for (; *pExtensions != '\0'; pExtensions = nextExtension(pExtensions)) {
- if (extensionEqual(pExtensions, pExtension)) {
- return true;
- }
- }
- return false;
-}
-
-/** End copied GL utility methods */
-
-bool checkGlError(JNIEnv* env) {
- int error;
- if ((error = glGetError()) != GL_NO_ERROR) {
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "GLES20 error: 0x%d", error);
- return true;
- }
- return false;
-}
-
-/**
- * Asynchronous low-overhead GL performance measurement using
- * http://www.khronos.org/registry/gles/extensions/EXT/EXT_disjoint_timer_query.txt
- *
- * Measures the duration of GPU processing for a set of GL commands, delivering
- * the measurement asynchronously once processing completes.
- *
- * All calls must come from a single thread with a valid GL context active.
- **/
-class PerfMeasurementContext {
- private:
- Vector<GLuint> mTimingQueries;
- size_t mTimingStartIndex;
- size_t mTimingEndIndex;
- size_t mTimingQueryIndex;
- size_t mFreeQueries;
-
- bool mInitDone;
- public:
-
- /**
- * maxQueryCount should be a conservative estimate of how many query objects
- * will be active at once, which is a function of the GPU's level of
- * pipelining and the frequency of queries.
- */
- explicit PerfMeasurementContext(size_t maxQueryCount):
- mTimingStartIndex(0),
- mTimingEndIndex(0),
- mTimingQueryIndex(0) {
- mTimingQueries.resize(maxQueryCount);
- mFreeQueries = maxQueryCount;
- mInitDone = false;
- }
-
- int getMaxQueryCount() {
- return mTimingQueries.size();
- }
-
- /**
- * Start a measurement period using the next available query object.
- * Returns INVALID_OPERATION if called multiple times in a row,
- * and BAD_VALUE if no more query objects are available.
- */
- int startGlTimer() {
- // Lazy init of queries to avoid needing GL context during construction
- if (!mInitDone) {
- glGenQueriesEXT(mTimingQueries.size(), mTimingQueries.editArray());
- mInitDone = true;
- }
-
- if (mTimingEndIndex != mTimingStartIndex) {
- return INVALID_OPERATION;
- }
-
- if (mFreeQueries == 0) {
- return BAD_VALUE;
- }
-
- glBeginQueryEXT(GL_TIME_ELAPSED_EXT, mTimingQueries[mTimingStartIndex]);
-
- mTimingStartIndex = (mTimingStartIndex + 1) % mTimingQueries.size();
- mFreeQueries--;
-
- return OK;
- }
-
- /**
- * Finish the current measurement period
- * Returns INVALID_OPERATION if called before any startGLTimer calls
- * or if called multiple times in a row.
- */
- int stopGlTimer() {
- size_t nextEndIndex = (mTimingEndIndex + 1) % mTimingQueries.size();
- if (nextEndIndex != mTimingStartIndex) {
- return INVALID_OPERATION;
- }
- glEndQueryEXT(GL_TIME_ELAPSED_EXT);
-
- mTimingEndIndex = nextEndIndex;
-
- return OK;
- }
-
- static const nsecs_t NO_DURATION_YET = -1L;
- static const nsecs_t FAILED_MEASUREMENT = -2L;
-
- /**
- * Get the next available duration measurement.
- *
- * Returns NO_DURATION_YET if no new measurement is available,
- * and FAILED_MEASUREMENT if an error occurred during the next
- * measurement period.
- *
- * Otherwise returns a positive number of nanoseconds measuring the
- * duration of the oldest completed query.
- */
- nsecs_t getNextGlDuration() {
- if (!mInitDone) {
- // No start/stop called yet
- return NO_DURATION_YET;
- }
-
- GLint available;
- glGetQueryObjectivEXT(mTimingQueries[mTimingQueryIndex],
- GL_QUERY_RESULT_AVAILABLE_EXT, &available);
- if (!available) {
- return NO_DURATION_YET;
- }
-
- GLint64 duration = FAILED_MEASUREMENT;
- GLint disjointOccurred;
- glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjointOccurred);
-
- if (!disjointOccurred) {
- glGetQueryObjecti64vEXT(mTimingQueries[mTimingQueryIndex],
- GL_QUERY_RESULT_EXT,
- &duration);
- }
-
- mTimingQueryIndex = (mTimingQueryIndex + 1) % mTimingQueries.size();
- mFreeQueries++;
-
- return static_cast<nsecs_t>(duration);
- }
-
- static bool isMeasurementSupported() {
- const GLubyte* extensions = glGetString(GL_EXTENSIONS);
- return checkForExtension(extensions,
- reinterpret_cast<const GLubyte*>("GL_EXT_disjoint_timer_query"));
- }
-
-};
-
-PerfMeasurementContext* getContext(jlong context) {
- return reinterpret_cast<PerfMeasurementContext*>(context);
-}
-
-extern "C" {
-
-static jlong PerfMeasurement_nativeCreateContext(JNIEnv* env, jobject thiz,
- jint maxQueryCount) {
- PerfMeasurementContext *context = new PerfMeasurementContext(maxQueryCount);
- return reinterpret_cast<jlong>(context);
-}
-
-static void PerfMeasurement_nativeDeleteContext(JNIEnv* env, jobject thiz,
- jlong contextHandle) {
- PerfMeasurementContext *context = getContext(contextHandle);
- delete(context);
-}
-
-static jboolean PerfMeasurement_nativeQuerySupport(JNIEnv* env, jobject thiz) {
- bool supported = PerfMeasurementContext::isMeasurementSupported();
- checkGlError(env);
- return static_cast<jboolean>(supported);
-}
-
-static void PerfMeasurement_nativeStartGlTimer(JNIEnv* env, jobject thiz,
- jlong contextHandle) {
-
- PerfMeasurementContext *context = getContext(contextHandle);
- status_t err = context->startGlTimer();
- if (err != OK) {
- switch (err) {
- case INVALID_OPERATION:
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "Mismatched start/end GL timing calls");
- return;
- case BAD_VALUE:
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "Too many timing queries in progress, max %d",
- context->getMaxQueryCount());
- return;
- default:
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "Unknown error starting GL timing");
- return;
- }
- }
- checkGlError(env);
-}
-
-static void PerfMeasurement_nativeStopGlTimer(JNIEnv* env, jobject thiz,
- jlong contextHandle) {
-
- PerfMeasurementContext *context = getContext(contextHandle);
- status_t err = context->stopGlTimer();
- if (err != OK) {
- switch (err) {
- case INVALID_OPERATION:
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "Mismatched start/end GL timing calls");
- return;
- default:
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "Unknown error ending GL timing");
- return;
- }
- }
- checkGlError(env);
-}
-
-static jlong PerfMeasurement_nativeGetNextGlDuration(JNIEnv* env,
- jobject thiz, jlong contextHandle) {
- PerfMeasurementContext *context = getContext(contextHandle);
- nsecs_t duration = context->getNextGlDuration();
-
- checkGlError(env);
- return static_cast<jlong>(duration);
-}
-
-} // extern "C"
-
-static const JNINativeMethod gPerfMeasurementMethods[] = {
- { "nativeCreateContext",
- "(I)J",
- (jlong *)PerfMeasurement_nativeCreateContext },
- { "nativeDeleteContext",
- "(J)V",
- (void *)PerfMeasurement_nativeDeleteContext },
- { "nativeQuerySupport",
- "()Z",
- (jboolean *)PerfMeasurement_nativeQuerySupport },
- { "nativeStartGlTimer",
- "(J)V",
- (void *)PerfMeasurement_nativeStartGlTimer },
- { "nativeStopGlTimer",
- "(J)V",
- (void *)PerfMeasurement_nativeStopGlTimer },
- { "nativeGetNextGlDuration",
- "(J)J",
- (jlong *)PerfMeasurement_nativeGetNextGlDuration }
-};
-
-
-// Get all the required offsets in java class and register native functions
-int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv* env)
-{
- // Register native functions
- return RegisterMethodsOrDie(env,
- PERF_MEASUREMENT_CLASS_NAME,
- gPerfMeasurementMethods,
- NELEM(gPerfMeasurementMethods));
-}
diff --git a/core/jni/android_hardware_camera2_utils_SurfaceUtils.cpp b/core/jni/android_hardware_camera2_utils_SurfaceUtils.cpp
new file mode 100644
index 0000000..2437a51
--- /dev/null
+++ b/core/jni/android_hardware_camera2_utils_SurfaceUtils.cpp
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#define LOG_TAG "Camera-SurfaceUtils-JNI"
+// #define LOG_NDEBUG 0
+#include <camera/CameraUtils.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+#include <utils/Trace.h>
+
+#include <nativehelper/JNIHelp.h>
+#include "android_runtime/android_graphics_SurfaceTexture.h"
+#include "android_runtime/android_view_Surface.h"
+#include "core_jni_helpers.h"
+#include "jni.h"
+
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/IProducerListener.h>
+#include <gui/Surface.h>
+#include <system/window.h>
+#include <ui/GraphicBuffer.h>
+
+#include <inttypes.h>
+#include <stdint.h>
+
+using namespace android;
+
+// fully-qualified class name
+#define CAMERA_UTILS_CLASS_NAME "android/hardware/camera2/utils/SurfaceUtils"
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
+
+#define OVERRIDE_SURFACE_ERROR(err) \
+ do { \
+ if (err == -ENODEV) { \
+ err = BAD_VALUE; \
+ } \
+ } while (0)
+
+static sp<ANativeWindow> getNativeWindow(JNIEnv* env, jobject surface) {
+ sp<ANativeWindow> anw;
+ if (surface) {
+ anw = android_view_Surface_getNativeWindow(env, surface);
+ if (env->ExceptionCheck()) {
+ return NULL;
+ }
+ } else {
+ jniThrowNullPointerException(env, "surface");
+ return NULL;
+ }
+ if (anw == NULL) {
+ ALOGE("%s: Surface had no valid native window.", __FUNCTION__);
+ return NULL;
+ }
+ return anw;
+}
+
+static sp<Surface> getSurface(JNIEnv* env, jobject surface) {
+ sp<Surface> s;
+ if (surface) {
+ s = android_view_Surface_getSurface(env, surface);
+ if (env->ExceptionCheck()) {
+ return NULL;
+ }
+ } else {
+ jniThrowNullPointerException(env, "surface");
+ return NULL;
+ }
+ if (s == NULL) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Surface had no valid native Surface.");
+ return NULL;
+ }
+ return s;
+}
+
+extern "C" {
+
+static jint SurfaceUtils_nativeDetectSurfaceType(JNIEnv* env, jobject thiz, jobject surface) {
+ ALOGV("nativeDetectSurfaceType");
+ sp<ANativeWindow> anw;
+ if ((anw = getNativeWindow(env, surface)) == NULL) {
+ ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
+ return BAD_VALUE;
+ }
+ int32_t fmt = 0;
+ status_t err = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &fmt);
+ if (err != NO_ERROR) {
+ ALOGE("%s: Error while querying surface pixel format %s (%d).", __FUNCTION__,
+ strerror(-err), err);
+ OVERRIDE_SURFACE_ERROR(err);
+ return err;
+ }
+ return fmt;
+}
+
+static jint SurfaceUtils_nativeDetectSurfaceDataspace(JNIEnv* env, jobject thiz, jobject surface) {
+ ALOGV("nativeDetectSurfaceDataspace");
+ sp<ANativeWindow> anw;
+ if ((anw = getNativeWindow(env, surface)) == NULL) {
+ ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
+ return BAD_VALUE;
+ }
+ int32_t fmt = 0;
+ status_t err = anw->query(anw.get(), NATIVE_WINDOW_DEFAULT_DATASPACE, &fmt);
+ if (err != NO_ERROR) {
+ ALOGE("%s: Error while querying surface dataspace %s (%d).", __FUNCTION__, strerror(-err),
+ err);
+ OVERRIDE_SURFACE_ERROR(err);
+ return err;
+ }
+ return fmt;
+}
+
+static jint SurfaceUtils_nativeDetectSurfaceDimens(JNIEnv* env, jobject thiz, jobject surface,
+ jintArray dimens) {
+ ALOGV("nativeGetSurfaceDimens");
+
+ if (dimens == NULL) {
+ ALOGE("%s: Null dimens argument passed to nativeDetectSurfaceDimens", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ if (env->GetArrayLength(dimens) < 2) {
+ ALOGE("%s: Invalid length of dimens argument in nativeDetectSurfaceDimens", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ sp<ANativeWindow> anw;
+ if ((anw = getNativeWindow(env, surface)) == NULL) {
+ ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
+ return BAD_VALUE;
+ }
+ int32_t dimenBuf[2];
+ status_t err = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, dimenBuf);
+ if (err != NO_ERROR) {
+ ALOGE("%s: Error while querying surface width %s (%d).", __FUNCTION__, strerror(-err), err);
+ OVERRIDE_SURFACE_ERROR(err);
+ return err;
+ }
+ err = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, dimenBuf + 1);
+ if (err != NO_ERROR) {
+ ALOGE("%s: Error while querying surface height %s (%d).", __FUNCTION__, strerror(-err),
+ err);
+ OVERRIDE_SURFACE_ERROR(err);
+ return err;
+ }
+ env->SetIntArrayRegion(dimens, /*start*/ 0, /*length*/ ARRAY_SIZE(dimenBuf), dimenBuf);
+ return NO_ERROR;
+}
+
+static jlong SurfaceUtils_nativeDetectSurfaceUsageFlags(JNIEnv* env, jobject thiz,
+ jobject surface) {
+ ALOGV("nativeDetectSurfaceUsageFlags");
+
+ sp<ANativeWindow> anw;
+ if ((anw = getNativeWindow(env, surface)) == NULL) {
+ jniThrowException(env, "java/lang/UnsupportedOperationException",
+ "Could not retrieve native window from surface.");
+ return BAD_VALUE;
+ }
+ uint64_t usage = 0;
+ status_t err = native_window_get_consumer_usage(anw.get(), &usage);
+ if (err != NO_ERROR) {
+ jniThrowException(env, "java/lang/UnsupportedOperationException",
+ "Error while querying surface usage bits");
+ OVERRIDE_SURFACE_ERROR(err);
+ return err;
+ }
+ return usage;
+}
+
+static jlong SurfaceUtils_nativeGetSurfaceId(JNIEnv* env, jobject thiz, jobject surface) {
+ ALOGV("nativeGetSurfaceId");
+ sp<Surface> s;
+ if ((s = getSurface(env, surface)) == NULL) {
+ ALOGE("%s: Could not retrieve native Surface from surface.", __FUNCTION__);
+ return 0;
+ }
+ sp<IGraphicBufferProducer> gbp = s->getIGraphicBufferProducer();
+ if (gbp == NULL) {
+ ALOGE("%s: Could not retrieve IGraphicBufferProducer from surface.", __FUNCTION__);
+ return 0;
+ }
+ sp<IBinder> b = IInterface::asBinder(gbp);
+ if (b == NULL) {
+ ALOGE("%s: Could not retrieve IBinder from surface.", __FUNCTION__);
+ return 0;
+ }
+ /*
+ * FIXME: Use better unique ID for surfaces than native IBinder pointer. Fix also in the camera
+ * service (CameraDeviceClient.h).
+ */
+ return reinterpret_cast<jlong>(b.get());
+}
+
+} // extern "C"
+
+static const JNINativeMethod gCameraSurfaceUtilsMethods[] = {
+ {"nativeDetectSurfaceType", "(Landroid/view/Surface;)I",
+ (void*)SurfaceUtils_nativeDetectSurfaceType},
+ {"nativeDetectSurfaceDataspace", "(Landroid/view/Surface;)I",
+ (void*)SurfaceUtils_nativeDetectSurfaceDataspace},
+ {"nativeDetectSurfaceDimens", "(Landroid/view/Surface;[I)I",
+ (void*)SurfaceUtils_nativeDetectSurfaceDimens},
+ {"nativeDetectSurfaceUsageFlags", "(Landroid/view/Surface;)J",
+ (void*)SurfaceUtils_nativeDetectSurfaceUsageFlags},
+ {"nativeGetSurfaceId", "(Landroid/view/Surface;)J", (void*)SurfaceUtils_nativeGetSurfaceId},
+};
+
+// Get all the required offsets in java class and register native functions
+int register_android_hardware_camera2_utils_SurfaceUtils(JNIEnv* env) {
+ // Register native functions
+ return RegisterMethodsOrDie(env, CAMERA_UTILS_CLASS_NAME, gCameraSurfaceUtilsMethods,
+ NELEM(gCameraSurfaceUtilsMethods));
+}
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index 6fd0c13..8153166 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -180,7 +180,8 @@
if (parcel) {
bool isInitialized = parcel->readInt32();
if (isInitialized) {
- sp<InputChannel> inputChannel = InputChannel::read(*parcel);
+ sp<InputChannel> inputChannel = new InputChannel();
+ inputChannel->readFromParcel(parcel);
NativeInputChannel* nativeInputChannel = new NativeInputChannel(inputChannel);
return reinterpret_cast<jlong>(nativeInputChannel);
}
@@ -203,7 +204,7 @@
return;
}
parcel->writeInt32(1); // initialized
- nativeInputChannel->getInputChannel()->write(*parcel);
+ nativeInputChannel->getInputChannel()->writeToParcel(parcel);
}
static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj, jlong channel) {
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index ec99684..f2f20e3a 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -236,6 +236,8 @@
optional int64 api_quota_schedule_window_ms = 33;
// Whether or not to throw an exception when an app hits its schedule quota limit.
optional bool api_quota_schedule_throw_exception = 34;
+ // Whether or not to return a failure result when an app hits its schedule quota limit.
+ optional bool api_quota_schedule_return_failure_result = 35;
message QuotaController {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -335,7 +337,7 @@
// In this time after screen turns on, we increase job concurrency.
optional int32 screen_off_job_concurrency_increase_delay_ms = 28;
- // Next tag: 35
+ // Next tag: 36
}
// Next tag: 4
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 485f5bf..506069c 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -424,9 +424,9 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"atzitu kokapen-hornitzaileen komando gehigarriak"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Kokapen-hornitzailearen agindu gehigarriak atzitzeko baimena ematen die aplikazioei. Horrela, agian aplikazioek GPSaren edo bestelako kokapenaren iturburuen funtzionamenduan eragina izan dezakete."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"lortu kokapen zehatza aurreko planoan bakarrik"</string>
- <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Erabiltzen ari zarenean, aplikazioak kokapen zehatza lor dezake kokapen-zerbitzuen bidez. Aplikazioak kokapena lortu ahal izateko, kokapen-zerbitzuek aktibatuta egon behar dute gailuan. Bateria-erabilera areagotzen du horrek."</string>
+ <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Abian denean, aplikazioak kokapen zehatza lor dezake kokapen-zerbitzuen bidez. Aplikazioak kokapena lortu ahal izateko, kokapen-zerbitzuek aktibatuta egon behar dute gailuan. Bateria-erabilera areagotzen du horrek."</string>
<string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"atzitu gutxi gorabeherako kokapena aurreko planoan bakarrik"</string>
- <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"Erabiltzen ari zarenean, aplikazioak gutxi gorabeherako kokapena lor dezake kokapen-zerbitzuen bidez. Aplikazioak kokapena lortu ahal izateko, kokapen-zerbitzuek aktibatuta egon behar dute gailuan."</string>
+ <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"Abian denean, aplikazioak gutxi gorabeherako kokapena lor dezake kokapen-zerbitzuen bidez. Aplikazioak kokapena lortu ahal izateko, kokapen-zerbitzuek aktibatuta egon behar dute gailuan."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"atzitu kokapena atzeko planoan"</string>
<string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"Aplikazioak kokapena atzi dezake, baita aplikazioa erabiltzen ari ez zarenean ere."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"aldatu audio-ezarpenak"</string>
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 3ecb1dd..0382dd3 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -42,4 +42,13 @@
<!-- Allow SystemUI to show the shutdown dialog -->
<bool name="config_showSysuiShutdown">true</bool>
+
+ <!-- Control the behavior when the user long presses the power button.
+ 0 - Nothing
+ 1 - Global actions menu
+ 2 - Power off (with confirmation)
+ 3 - Power off (without confirmation)
+ 4 - Go to voice assist
+ 5 - Go to assistant (Settings.Secure.ASSISTANT -->
+ <integer name="config_longPressOnPowerBehavior">3</integer>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 367cff9..12275dc 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4828,10 +4828,10 @@
<string name="confirm_battery_saver">OK</string>
<!-- [CHAR_LIMIT=NONE] Battery saver: Feature description, with a "learn more" link. -->
- <string name="battery_saver_description_with_learn_more">To extend battery life, Battery Saver:\n\n\u2022Turns on Dark theme\n\u2022Turns off or restricts background activity, some visual effects, and other features like \u201cHey Google\u201d\n\n<annotation id="url">Learn more</annotation></string>
+ <string name="battery_saver_description_with_learn_more">To extend battery life, Battery Saver:\n\n\u2022 Turns on Dark theme\n\u2022 Turns off or restricts background activity, some visual effects, and other features like \u201cHey Google\u201d\n\n<annotation id="url">Learn more</annotation></string>
<!-- [CHAR_LIMIT=NONE] Battery saver: Feature description, without a "learn more" link. -->
- <string name="battery_saver_description">To extend battery life, Battery Saver:\n\n\u2022Turns on Dark theme\n\u2022Turns off or restricts background activity, some visual effects, and other features like \u201cHey Google\u201d</string>
+ <string name="battery_saver_description">To extend battery life, Battery Saver:\n\n\u2022 Turns on Dark theme\n\u2022 Turns off or restricts background activity, some visual effects, and other features like \u201cHey Google\u201d</string>
<!-- [CHAR_LIMIT=NONE] Data saver: Feature description -->
<string name="data_saver_description">To help reduce data usage, Data Saver prevents some apps from sending or receiving data in the background. An app you’re currently using can access data, but may do so less frequently. This may mean, for example, that images don’t display until you tap them.</string>
diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java
index 5a0a84d..fa1d56f 100644
--- a/core/tests/coretests/src/android/text/format/DateFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java
@@ -16,8 +16,10 @@
package android.text.format;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.platform.test.annotations.Presubmit;
@@ -27,6 +29,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
import java.util.Locale;
@Presubmit
@@ -55,4 +58,71 @@
assertFalse(DateFormat.is24HourLocale(Locale.US));
assertTrue(DateFormat.is24HourLocale(Locale.GERMANY));
}
+
+ @Test
+ public void testGetDateFormatOrder() {
+ // lv and fa use differing orders depending on whether you're using numeric or
+ // textual months.
+ Locale lv = new Locale("lv");
+ assertEquals("[d, M, y]", Arrays.toString(DateFormat.getDateFormatOrder(
+ best(lv, "yyyy-M-dd"))));
+ assertEquals("[y, d, M]", Arrays.toString(DateFormat.getDateFormatOrder(
+ best(lv, "yyyy-MMM-dd"))));
+ assertEquals("[d, M, \u0000]", Arrays.toString(DateFormat.getDateFormatOrder(
+ best(lv, "MMM-dd"))));
+ Locale fa = new Locale("fa");
+ assertEquals("[y, M, d]", Arrays.toString(DateFormat.getDateFormatOrder(
+ best(fa, "yyyy-M-dd"))));
+ assertEquals("[d, M, y]", Arrays.toString(DateFormat.getDateFormatOrder(
+ best(fa, "yyyy-MMM-dd"))));
+ assertEquals("[d, M, \u0000]", Arrays.toString(DateFormat.getDateFormatOrder(
+ best(fa, "MMM-dd"))));
+
+ // English differs on each side of the Atlantic.
+ Locale enUS = Locale.US;
+ assertEquals("[M, d, y]", Arrays.toString(DateFormat.getDateFormatOrder(
+ best(enUS, "yyyy-M-dd"))));
+ assertEquals("[M, d, y]", Arrays.toString(DateFormat.getDateFormatOrder(
+ best(enUS, "yyyy-MMM-dd"))));
+ assertEquals("[M, d, \u0000]", Arrays.toString(DateFormat.getDateFormatOrder(
+ best(enUS, "MMM-dd"))));
+ Locale enGB = Locale.UK;
+ assertEquals("[d, M, y]", Arrays.toString(DateFormat.getDateFormatOrder(
+ best(enGB, "yyyy-M-dd"))));
+ assertEquals("[d, M, y]", Arrays.toString(DateFormat.getDateFormatOrder(
+ best(enGB, "yyyy-MMM-dd"))));
+ assertEquals("[d, M, \u0000]", Arrays.toString(DateFormat.getDateFormatOrder(
+ best(enGB, "MMM-dd"))));
+
+ assertEquals("[y, M, d]", Arrays.toString(DateFormat.getDateFormatOrder(
+ "yyyy - 'why' '' 'ddd' MMM-dd")));
+
+ try {
+ DateFormat.getDateFormatOrder("the quick brown fox jumped over the lazy dog");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ DateFormat.getDateFormatOrder("'");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ DateFormat.getDateFormatOrder("yyyy'");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ DateFormat.getDateFormatOrder("yyyy'MMM");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ private static String best(Locale l, String skeleton) {
+ return DateFormat.getBestDateTimePattern(l, skeleton);
+ }
}
diff --git a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
new file mode 100644
index 0000000..d9ba8fb
--- /dev/null
+++ b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
@@ -0,0 +1,762 @@
+/*
+ * Copyright (C) 2015 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.text.format;
+
+import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_ALL;
+import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_RELATIVE;
+import static android.text.format.DateUtilsBridge.FORMAT_NO_YEAR;
+import static android.text.format.DateUtilsBridge.FORMAT_NUMERIC_DATE;
+import static android.text.format.DateUtilsBridge.FORMAT_SHOW_YEAR;
+import static android.text.format.RelativeDateTimeFormatter.DAY_IN_MILLIS;
+import static android.text.format.RelativeDateTimeFormatter.HOUR_IN_MILLIS;
+import static android.text.format.RelativeDateTimeFormatter.MINUTE_IN_MILLIS;
+import static android.text.format.RelativeDateTimeFormatter.SECOND_IN_MILLIS;
+import static android.text.format.RelativeDateTimeFormatter.WEEK_IN_MILLIS;
+import static android.text.format.RelativeDateTimeFormatter.YEAR_IN_MILLIS;
+import static android.text.format.RelativeDateTimeFormatter.getRelativeDateTimeString;
+import static android.text.format.RelativeDateTimeFormatter.getRelativeTimeSpanString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RelativeDateTimeFormatterTest {
+
+ // Tests adopted from CTS tests for DateUtils.getRelativeTimeSpanString.
+ @Test
+ public void test_getRelativeTimeSpanStringCTS() throws Exception {
+ Locale en_US = new Locale("en", "US");
+ TimeZone tz = TimeZone.getTimeZone("GMT");
+ Calendar cal = Calendar.getInstance(tz, en_US);
+ // Feb 5, 2015 at 10:50 GMT
+ cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0);
+ final long baseTime = cal.getTimeInMillis();
+
+ assertEquals("0 minutes ago",
+ getRelativeTimeSpanString(en_US, tz, baseTime - SECOND_IN_MILLIS, baseTime,
+ MINUTE_IN_MILLIS, 0));
+ assertEquals("In 0 minutes",
+ getRelativeTimeSpanString(en_US, tz, baseTime + SECOND_IN_MILLIS, baseTime,
+ MINUTE_IN_MILLIS, 0));
+
+ assertEquals("1 minute ago",
+ getRelativeTimeSpanString(en_US, tz, 0, MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 0));
+ assertEquals("In 1 minute",
+ getRelativeTimeSpanString(en_US, tz, MINUTE_IN_MILLIS, 0, MINUTE_IN_MILLIS, 0));
+
+ assertEquals("42 minutes ago",
+ getRelativeTimeSpanString(en_US, tz, baseTime - 42 * MINUTE_IN_MILLIS, baseTime,
+ MINUTE_IN_MILLIS, 0));
+ assertEquals("In 42 minutes",
+ getRelativeTimeSpanString(en_US, tz, baseTime + 42 * MINUTE_IN_MILLIS, baseTime,
+ MINUTE_IN_MILLIS, 0));
+
+ final long TWO_HOURS_IN_MS = 2 * HOUR_IN_MILLIS;
+ assertEquals("2 hours ago",
+ getRelativeTimeSpanString(en_US, tz, baseTime - TWO_HOURS_IN_MS, baseTime,
+ MINUTE_IN_MILLIS, FORMAT_NUMERIC_DATE));
+ assertEquals("In 2 hours",
+ getRelativeTimeSpanString(en_US, tz, baseTime + TWO_HOURS_IN_MS, baseTime,
+ MINUTE_IN_MILLIS, FORMAT_NUMERIC_DATE));
+
+ assertEquals("In 42 min.",
+ getRelativeTimeSpanString(en_US, tz, baseTime + (42 * MINUTE_IN_MILLIS), baseTime,
+ MINUTE_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+
+ assertEquals("Tomorrow",
+ getRelativeTimeSpanString(en_US, tz, DAY_IN_MILLIS, 0, DAY_IN_MILLIS, 0));
+ assertEquals("In 2 days",
+ getRelativeTimeSpanString(en_US, tz, 2 * DAY_IN_MILLIS, 0, DAY_IN_MILLIS, 0));
+ assertEquals("Yesterday",
+ getRelativeTimeSpanString(en_US, tz, 0, DAY_IN_MILLIS, DAY_IN_MILLIS, 0));
+ assertEquals("2 days ago",
+ getRelativeTimeSpanString(en_US, tz, 0, 2 * DAY_IN_MILLIS, DAY_IN_MILLIS, 0));
+
+ final long DAY_DURATION = 5 * 24 * 60 * 60 * 1000;
+ assertEquals("5 days ago",
+ getRelativeTimeSpanString(en_US, tz, baseTime - DAY_DURATION, baseTime,
+ DAY_IN_MILLIS, 0));
+ }
+
+ private void test_getRelativeTimeSpanString_helper(long delta, long minResolution, int flags,
+ String expectedInPast,
+ String expectedInFuture) throws Exception {
+ Locale en_US = new Locale("en", "US");
+ TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
+ Calendar cal = Calendar.getInstance(tz, en_US);
+ // Feb 5, 2015 at 10:50 PST
+ cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0);
+ final long base = cal.getTimeInMillis();
+
+ assertEquals(expectedInPast,
+ getRelativeTimeSpanString(en_US, tz, base - delta, base, minResolution, flags));
+ assertEquals(expectedInFuture,
+ getRelativeTimeSpanString(en_US, tz, base + delta, base, minResolution, flags));
+ }
+
+ private void test_getRelativeTimeSpanString_helper(long delta, long minResolution,
+ String expectedInPast,
+ String expectedInFuture) throws Exception {
+ test_getRelativeTimeSpanString_helper(delta, minResolution, 0, expectedInPast,
+ expectedInFuture);
+ }
+
+ @Test
+ public void test_getRelativeTimeSpanString() throws Exception {
+
+ test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, 0, "0 seconds ago",
+ "0 seconds ago");
+ test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, "1 minute ago",
+ "In 1 minute");
+ test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, "1 minute ago",
+ "In 1 minute");
+ test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, 0, "5 days ago", "In 5 days");
+
+ test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, SECOND_IN_MILLIS,
+ "0 seconds ago",
+ "0 seconds ago");
+ test_getRelativeTimeSpanString_helper(1 * SECOND_IN_MILLIS, SECOND_IN_MILLIS,
+ "1 second ago",
+ "In 1 second");
+ test_getRelativeTimeSpanString_helper(2 * SECOND_IN_MILLIS, SECOND_IN_MILLIS,
+ "2 seconds ago",
+ "In 2 seconds");
+ test_getRelativeTimeSpanString_helper(25 * SECOND_IN_MILLIS, SECOND_IN_MILLIS,
+ "25 seconds ago",
+ "In 25 seconds");
+ test_getRelativeTimeSpanString_helper(75 * SECOND_IN_MILLIS, SECOND_IN_MILLIS,
+ "1 minute ago",
+ "In 1 minute");
+ test_getRelativeTimeSpanString_helper(5000 * SECOND_IN_MILLIS, SECOND_IN_MILLIS,
+ "1 hour ago",
+ "In 1 hour");
+
+ test_getRelativeTimeSpanString_helper(0 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS,
+ "0 minutes ago",
+ "0 minutes ago");
+ test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS,
+ "1 minute ago",
+ "In 1 minute");
+ test_getRelativeTimeSpanString_helper(2 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS,
+ "2 minutes ago",
+ "In 2 minutes");
+ test_getRelativeTimeSpanString_helper(25 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS,
+ "25 minutes ago",
+ "In 25 minutes");
+ test_getRelativeTimeSpanString_helper(75 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "1 hour ago",
+ "In 1 hour");
+ test_getRelativeTimeSpanString_helper(720 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS,
+ "12 hours ago",
+ "In 12 hours");
+
+ test_getRelativeTimeSpanString_helper(0 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "0 hours ago",
+ "0 hours ago");
+ test_getRelativeTimeSpanString_helper(1 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "1 hour ago",
+ "In 1 hour");
+ test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "2 hours ago",
+ "In 2 hours");
+ test_getRelativeTimeSpanString_helper(5 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "5 hours ago",
+ "In 5 hours");
+ test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "20 hours ago",
+ "In 20 hours");
+
+ test_getRelativeTimeSpanString_helper(0 * DAY_IN_MILLIS, DAY_IN_MILLIS, "Today", "Today");
+ test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday",
+ "Tomorrow");
+ test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday",
+ "Tomorrow");
+ test_getRelativeTimeSpanString_helper(2 * DAY_IN_MILLIS, DAY_IN_MILLIS, "2 days ago",
+ "In 2 days");
+ test_getRelativeTimeSpanString_helper(25 * DAY_IN_MILLIS, DAY_IN_MILLIS, "January 11",
+ "March 2");
+
+ test_getRelativeTimeSpanString_helper(0 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "0 weeks ago",
+ "0 weeks ago");
+ test_getRelativeTimeSpanString_helper(1 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "1 week ago",
+ "In 1 week");
+ test_getRelativeTimeSpanString_helper(2 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "2 weeks ago",
+ "In 2 weeks");
+ test_getRelativeTimeSpanString_helper(25 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "25 weeks ago",
+ "In 25 weeks");
+
+ // duration >= minResolution
+ test_getRelativeTimeSpanString_helper(30 * SECOND_IN_MILLIS, 0, "30 seconds ago",
+ "In 30 seconds");
+ test_getRelativeTimeSpanString_helper(30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS,
+ "30 minutes ago", "In 30 minutes");
+ test_getRelativeTimeSpanString_helper(30 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, "Yesterday",
+ "Tomorrow");
+ test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, "5 days ago",
+ "In 5 days");
+ test_getRelativeTimeSpanString_helper(30 * WEEK_IN_MILLIS, MINUTE_IN_MILLIS,
+ "July 10, 2014",
+ "September 3");
+ test_getRelativeTimeSpanString_helper(5 * 365 * DAY_IN_MILLIS, MINUTE_IN_MILLIS,
+ "February 6, 2010", "February 4, 2020");
+
+ test_getRelativeTimeSpanString_helper(60 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS,
+ "1 minute ago",
+ "In 1 minute");
+ test_getRelativeTimeSpanString_helper(120 * SECOND_IN_MILLIS - 1, MINUTE_IN_MILLIS,
+ "1 minute ago", "In 1 minute");
+ test_getRelativeTimeSpanString_helper(60 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, "1 hour ago",
+ "In 1 hour");
+ test_getRelativeTimeSpanString_helper(120 * MINUTE_IN_MILLIS - 1, HOUR_IN_MILLIS,
+ "1 hour ago",
+ "In 1 hour");
+ test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Today", "Today");
+ test_getRelativeTimeSpanString_helper(12 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday",
+ "Today");
+ test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday",
+ "Tomorrow");
+ test_getRelativeTimeSpanString_helper(48 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "2 days ago",
+ "In 2 days");
+ test_getRelativeTimeSpanString_helper(45 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "2 days ago",
+ "In 2 days");
+ test_getRelativeTimeSpanString_helper(7 * DAY_IN_MILLIS, WEEK_IN_MILLIS, "1 week ago",
+ "In 1 week");
+ test_getRelativeTimeSpanString_helper(14 * DAY_IN_MILLIS - 1, WEEK_IN_MILLIS, "1 week ago",
+ "In 1 week");
+
+ // duration < minResolution
+ test_getRelativeTimeSpanString_helper(59 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS,
+ "0 minutes ago",
+ "In 0 minutes");
+ test_getRelativeTimeSpanString_helper(59 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, "0 hours ago",
+ "In 0 hours");
+ test_getRelativeTimeSpanString_helper(HOUR_IN_MILLIS - 1, HOUR_IN_MILLIS, "0 hours ago",
+ "In 0 hours");
+ test_getRelativeTimeSpanString_helper(DAY_IN_MILLIS - 1, DAY_IN_MILLIS, "Yesterday",
+ "Tomorrow");
+ test_getRelativeTimeSpanString_helper(20 * SECOND_IN_MILLIS, WEEK_IN_MILLIS, "0 weeks ago",
+ "In 0 weeks");
+ test_getRelativeTimeSpanString_helper(WEEK_IN_MILLIS - 1, WEEK_IN_MILLIS, "0 weeks ago",
+ "In 0 weeks");
+ }
+
+ @Test
+ public void test_getRelativeTimeSpanStringAbbrev() throws Exception {
+ int flags = FORMAT_ABBREV_RELATIVE;
+
+ test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, 0, flags, "0 sec. ago",
+ "0 sec. ago");
+ test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, flags, "1 min. ago",
+ "In 1 min.");
+ test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, 0, flags, "5 days ago",
+ "In 5 days");
+
+ test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags,
+ "0 sec. ago", "0 sec. ago");
+ test_getRelativeTimeSpanString_helper(1 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags,
+ "1 sec. ago", "In 1 sec.");
+ test_getRelativeTimeSpanString_helper(2 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags,
+ "2 sec. ago", "In 2 sec.");
+ test_getRelativeTimeSpanString_helper(25 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags,
+ "25 sec. ago", "In 25 sec.");
+ test_getRelativeTimeSpanString_helper(75 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags,
+ "1 min. ago", "In 1 min.");
+ test_getRelativeTimeSpanString_helper(5000 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags,
+ "1 hr. ago", "In 1 hr.");
+
+ test_getRelativeTimeSpanString_helper(0 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+ "0 min. ago", "0 min. ago");
+ test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+ "1 min. ago", "In 1 min.");
+ test_getRelativeTimeSpanString_helper(2 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+ "2 min. ago", "In 2 min.");
+ test_getRelativeTimeSpanString_helper(25 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+ "25 min. ago", "In 25 min.");
+ test_getRelativeTimeSpanString_helper(75 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+ "1 hr. ago", "In 1 hr.");
+ test_getRelativeTimeSpanString_helper(720 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+ "12 hr. ago", "In 12 hr.");
+
+ test_getRelativeTimeSpanString_helper(0 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags,
+ "0 hr. ago", "0 hr. ago");
+ test_getRelativeTimeSpanString_helper(1 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags,
+ "1 hr. ago", "In 1 hr.");
+ test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags,
+ "2 hr. ago", "In 2 hr.");
+ test_getRelativeTimeSpanString_helper(5 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags,
+ "5 hr. ago", "In 5 hr.");
+ test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags,
+ "20 hr. ago", "In 20 hr.");
+
+ test_getRelativeTimeSpanString_helper(0 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags, "Today",
+ "Today");
+ test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags,
+ "Yesterday", "Tomorrow");
+ test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags,
+ "Yesterday", "Tomorrow");
+ test_getRelativeTimeSpanString_helper(2 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags,
+ "2 days ago", "In 2 days");
+ test_getRelativeTimeSpanString_helper(25 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags,
+ "January 11", "March 2");
+
+ test_getRelativeTimeSpanString_helper(0 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags,
+ "0 wk. ago", "0 wk. ago");
+ test_getRelativeTimeSpanString_helper(1 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags,
+ "1 wk. ago", "In 1 wk.");
+ test_getRelativeTimeSpanString_helper(2 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags,
+ "2 wk. ago", "In 2 wk.");
+ test_getRelativeTimeSpanString_helper(25 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags,
+ "25 wk. ago", "In 25 wk.");
+
+ // duration >= minResolution
+ test_getRelativeTimeSpanString_helper(30 * SECOND_IN_MILLIS, 0, flags, "30 sec. ago",
+ "In 30 sec.");
+ test_getRelativeTimeSpanString_helper(30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+ "30 min. ago", "In 30 min.");
+ test_getRelativeTimeSpanString_helper(30 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+ "Yesterday", "Tomorrow");
+ test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+ "5 days ago", "In 5 days");
+ test_getRelativeTimeSpanString_helper(30 * WEEK_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+ "July 10, 2014", "September 3");
+ test_getRelativeTimeSpanString_helper(5 * 365 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+ "February 6, 2010", "February 4, 2020");
+
+ test_getRelativeTimeSpanString_helper(60 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+ "1 min. ago", "In 1 min.");
+ test_getRelativeTimeSpanString_helper(120 * SECOND_IN_MILLIS - 1, MINUTE_IN_MILLIS, flags,
+ "1 min. ago", "In 1 min.");
+ test_getRelativeTimeSpanString_helper(60 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, flags,
+ "1 hr. ago", "In 1 hr.");
+ test_getRelativeTimeSpanString_helper(120 * MINUTE_IN_MILLIS - 1, HOUR_IN_MILLIS, flags,
+ "1 hr. ago", "In 1 hr.");
+ test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, "Today",
+ "Today");
+ test_getRelativeTimeSpanString_helper(12 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags,
+ "Yesterday", "Today");
+ test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags,
+ "Yesterday", "Tomorrow");
+ test_getRelativeTimeSpanString_helper(48 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags,
+ "2 days ago", "In 2 days");
+ test_getRelativeTimeSpanString_helper(45 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags,
+ "2 days ago", "In 2 days");
+ test_getRelativeTimeSpanString_helper(7 * DAY_IN_MILLIS, WEEK_IN_MILLIS, flags,
+ "1 wk. ago", "In 1 wk.");
+ test_getRelativeTimeSpanString_helper(14 * DAY_IN_MILLIS - 1, WEEK_IN_MILLIS, flags,
+ "1 wk. ago", "In 1 wk.");
+
+ // duration < minResolution
+ test_getRelativeTimeSpanString_helper(59 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+ "0 min. ago", "In 0 min.");
+ test_getRelativeTimeSpanString_helper(59 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, flags,
+ "0 hr. ago", "In 0 hr.");
+ test_getRelativeTimeSpanString_helper(HOUR_IN_MILLIS - 1, HOUR_IN_MILLIS, flags,
+ "0 hr. ago", "In 0 hr.");
+ test_getRelativeTimeSpanString_helper(DAY_IN_MILLIS - 1, DAY_IN_MILLIS, flags,
+ "Yesterday", "Tomorrow");
+ test_getRelativeTimeSpanString_helper(20 * SECOND_IN_MILLIS, WEEK_IN_MILLIS, flags,
+ "0 wk. ago", "In 0 wk.");
+ test_getRelativeTimeSpanString_helper(WEEK_IN_MILLIS - 1, WEEK_IN_MILLIS, flags,
+ "0 wk. ago", "In 0 wk.");
+
+ }
+
+ @Test
+ public void test_getRelativeTimeSpanStringGerman() throws Exception {
+ // Bug: 19744876
+ // We need to specify the timezone and the time explicitly. Otherwise it
+ // may not always give a correct answer of "tomorrow" by using
+ // (now + DAY_IN_MILLIS).
+ Locale de_DE = new Locale("de", "DE");
+ TimeZone tz = TimeZone.getTimeZone("Europe/Berlin");
+ Calendar cal = Calendar.getInstance(tz, de_DE);
+ // Feb 5, 2015 at 10:50 CET
+ cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0);
+ final long now = cal.getTimeInMillis();
+
+ // 42 minutes ago
+ assertEquals("Vor 42 Minuten", getRelativeTimeSpanString(de_DE, tz,
+ now - 42 * MINUTE_IN_MILLIS, now, MINUTE_IN_MILLIS, 0));
+ // In 42 minutes
+ assertEquals("In 42 Minuten", getRelativeTimeSpanString(de_DE, tz,
+ now + 42 * MINUTE_IN_MILLIS, now, MINUTE_IN_MILLIS, 0));
+ // Yesterday
+ assertEquals("Gestern", getRelativeTimeSpanString(de_DE, tz,
+ now - DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0));
+ // The day before yesterday
+ assertEquals("Vorgestern", getRelativeTimeSpanString(de_DE, tz,
+ now - 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0));
+ // Tomorrow
+ assertEquals("Morgen", getRelativeTimeSpanString(de_DE, tz,
+ now + DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0));
+ // The day after tomorrow
+ assertEquals("Übermorgen", getRelativeTimeSpanString(de_DE, tz,
+ now + 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0));
+ }
+
+ @Test
+ public void test_getRelativeTimeSpanStringFrench() throws Exception {
+ Locale fr_FR = new Locale("fr", "FR");
+ TimeZone tz = TimeZone.getTimeZone("Europe/Paris");
+ Calendar cal = Calendar.getInstance(tz, fr_FR);
+ // Feb 5, 2015 at 10:50 CET
+ cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0);
+ final long now = cal.getTimeInMillis();
+
+ // 42 minutes ago
+ assertEquals("Il y a 42 minutes", getRelativeTimeSpanString(fr_FR, tz,
+ now - (42 * MINUTE_IN_MILLIS), now, MINUTE_IN_MILLIS, 0));
+ // In 42 minutes
+ assertEquals("Dans 42 minutes", getRelativeTimeSpanString(fr_FR, tz,
+ now + (42 * MINUTE_IN_MILLIS), now, MINUTE_IN_MILLIS, 0));
+ // Yesterday
+ assertEquals("Hier", getRelativeTimeSpanString(fr_FR, tz,
+ now - DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0));
+ // The day before yesterday
+ assertEquals("Avant-hier", getRelativeTimeSpanString(fr_FR, tz,
+ now - 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0));
+ // Tomorrow
+ assertEquals("Demain", getRelativeTimeSpanString(fr_FR, tz,
+ now + DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0));
+ // The day after tomorrow
+ assertEquals("Après-demain", getRelativeTimeSpanString(fr_FR, tz,
+ now + 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0));
+ }
+
+ // Tests adopted from CTS tests for DateUtils.getRelativeDateTimeString.
+ @Test
+ public void test_getRelativeDateTimeStringCTS() throws Exception {
+ Locale en_US = Locale.getDefault();
+ TimeZone tz = TimeZone.getDefault();
+ final long baseTime = System.currentTimeMillis();
+
+ final long DAY_DURATION = 5 * 24 * 60 * 60 * 1000;
+ assertNotNull(getRelativeDateTimeString(en_US, tz, baseTime - DAY_DURATION, baseTime,
+ MINUTE_IN_MILLIS, DAY_IN_MILLIS,
+ FORMAT_NUMERIC_DATE));
+ }
+
+ @Test
+ public void test_getRelativeDateTimeString() throws Exception {
+ Locale en_US = new Locale("en", "US");
+ TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
+ Calendar cal = Calendar.getInstance(tz, en_US);
+ // Feb 5, 2015 at 10:50 PST
+ cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0);
+ final long base = cal.getTimeInMillis();
+
+ assertEquals("5 seconds ago, 10:49 AM",
+ getRelativeDateTimeString(en_US, tz, base - 5 * SECOND_IN_MILLIS, base, 0,
+ MINUTE_IN_MILLIS, 0));
+ assertEquals("5 min. ago, 10:45 AM",
+ getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base, 0,
+ HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+ assertEquals("0 hr. ago, 10:45 AM",
+ getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base,
+ HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+ assertEquals("5 hours ago, 5:50 AM",
+ getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base,
+ HOUR_IN_MILLIS, DAY_IN_MILLIS, 0));
+ assertEquals("Yesterday, 7:50 PM",
+ getRelativeDateTimeString(en_US, tz, base - 15 * HOUR_IN_MILLIS, base, 0,
+ WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+ assertEquals("5 days ago, 10:50 AM",
+ getRelativeDateTimeString(en_US, tz, base - 5 * DAY_IN_MILLIS, base, 0,
+ WEEK_IN_MILLIS, 0));
+ assertEquals("Jan 29, 10:50 AM",
+ getRelativeDateTimeString(en_US, tz, base - 7 * DAY_IN_MILLIS, base, 0,
+ WEEK_IN_MILLIS, 0));
+ assertEquals("11/27/2014, 10:50 AM",
+ getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
+ WEEK_IN_MILLIS, 0));
+ assertEquals("11/27/2014, 10:50 AM",
+ getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
+ YEAR_IN_MILLIS, 0));
+
+ // User-supplied flags should be ignored when formatting the date clause.
+ final int FORMAT_SHOW_WEEKDAY = 0x00002;
+ assertEquals("11/27/2014, 10:50 AM",
+ getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
+ WEEK_IN_MILLIS,
+ FORMAT_ABBREV_ALL | FORMAT_SHOW_WEEKDAY));
+ }
+
+ @Test
+ public void test_getRelativeDateTimeStringDST() throws Exception {
+ Locale en_US = new Locale("en", "US");
+ TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
+ Calendar cal = Calendar.getInstance(tz, en_US);
+
+ // DST starts on Mar 9, 2014 at 2:00 AM.
+ // So 5 hours before 3:15 AM should be formatted as 'Yesterday, 9:15 PM'.
+ cal.set(2014, Calendar.MARCH, 9, 3, 15, 0);
+ long base = cal.getTimeInMillis();
+ assertEquals("Yesterday, 9:15 PM",
+ getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base, 0,
+ WEEK_IN_MILLIS, 0));
+
+ // 1 hour after 2:00 AM should be formatted as 'In 1 hour, 4:00 AM'.
+ cal.set(2014, Calendar.MARCH, 9, 2, 0, 0);
+ base = cal.getTimeInMillis();
+ assertEquals("In 1 hour, 4:00 AM",
+ getRelativeDateTimeString(en_US, tz, base + 1 * HOUR_IN_MILLIS, base, 0,
+ WEEK_IN_MILLIS, 0));
+
+ // DST ends on Nov 2, 2014 at 2:00 AM. Clocks are turned backward 1 hour to
+ // 1:00 AM. 8 hours before 5:20 AM should be 'Yesterday, 10:20 PM'.
+ cal.set(2014, Calendar.NOVEMBER, 2, 5, 20, 0);
+ base = cal.getTimeInMillis();
+ assertEquals("Yesterday, 10:20 PM",
+ getRelativeDateTimeString(en_US, tz, base - 8 * HOUR_IN_MILLIS, base, 0,
+ WEEK_IN_MILLIS, 0));
+
+ cal.set(2014, Calendar.NOVEMBER, 2, 0, 45, 0);
+ base = cal.getTimeInMillis();
+ // 45 minutes after 0:45 AM should be 'In 45 minutes, 1:30 AM'.
+ assertEquals("In 45 minutes, 1:30 AM",
+ getRelativeDateTimeString(en_US, tz, base + 45 * MINUTE_IN_MILLIS, base, 0,
+ WEEK_IN_MILLIS, 0));
+ // 45 minutes later, it should be 'In 45 minutes, 1:15 AM'.
+ assertEquals("In 45 minutes, 1:15 AM",
+ getRelativeDateTimeString(en_US, tz, base + 90 * MINUTE_IN_MILLIS,
+ base + 45 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0));
+ // Another 45 minutes later, it should be 'In 45 minutes, 2:00 AM'.
+ assertEquals("In 45 minutes, 2:00 AM",
+ getRelativeDateTimeString(en_US, tz, base + 135 * MINUTE_IN_MILLIS,
+ base + 90 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0));
+ }
+
+ @Test
+ public void test_getRelativeDateTimeStringItalian() throws Exception {
+ Locale it_IT = new Locale("it", "IT");
+ TimeZone tz = TimeZone.getTimeZone("Europe/Rome");
+ Calendar cal = Calendar.getInstance(tz, it_IT);
+ // 05 febbraio 2015 20:15
+ cal.set(2015, Calendar.FEBRUARY, 5, 20, 15, 0);
+ final long base = cal.getTimeInMillis();
+
+ assertEquals("5 secondi fa, 20:14",
+ getRelativeDateTimeString(it_IT, tz, base - 5 * SECOND_IN_MILLIS, base, 0,
+ MINUTE_IN_MILLIS, 0));
+ assertEquals("5 min fa, 20:10",
+ getRelativeDateTimeString(it_IT, tz, base - 5 * MINUTE_IN_MILLIS, base, 0,
+ HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+ assertEquals("0 h fa, 20:10",
+ getRelativeDateTimeString(it_IT, tz, base - 5 * MINUTE_IN_MILLIS, base,
+ HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+ assertEquals("Ieri, 22:15",
+ getRelativeDateTimeString(it_IT, tz, base - 22 * HOUR_IN_MILLIS, base, 0,
+ WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+ assertEquals("5 giorni fa, 20:15",
+ getRelativeDateTimeString(it_IT, tz, base - 5 * DAY_IN_MILLIS, base, 0,
+ WEEK_IN_MILLIS, 0));
+ assertEquals("27/11/2014, 20:15",
+ getRelativeDateTimeString(it_IT, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
+ WEEK_IN_MILLIS, 0));
+ }
+
+ // http://b/5252772: detect the actual date difference
+ @Test
+ public void test5252772() throws Exception {
+ Locale en_US = new Locale("en", "US");
+ TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
+
+ // Now is Sep 2, 2011, 10:23 AM PDT.
+ Calendar nowCalendar = Calendar.getInstance(tz, en_US);
+ nowCalendar.set(2011, Calendar.SEPTEMBER, 2, 10, 23, 0);
+ final long now = nowCalendar.getTimeInMillis();
+
+ // Sep 1, 2011, 10:24 AM
+ Calendar yesterdayCalendar1 = Calendar.getInstance(tz, en_US);
+ yesterdayCalendar1.set(2011, Calendar.SEPTEMBER, 1, 10, 24, 0);
+ long yesterday1 = yesterdayCalendar1.getTimeInMillis();
+ assertEquals("Yesterday, 10:24 AM",
+ getRelativeDateTimeString(en_US, tz, yesterday1, now, MINUTE_IN_MILLIS,
+ WEEK_IN_MILLIS, 0));
+
+ // Sep 1, 2011, 10:22 AM
+ Calendar yesterdayCalendar2 = Calendar.getInstance(tz, en_US);
+ yesterdayCalendar2.set(2011, Calendar.SEPTEMBER, 1, 10, 22, 0);
+ long yesterday2 = yesterdayCalendar2.getTimeInMillis();
+ assertEquals("Yesterday, 10:22 AM",
+ getRelativeDateTimeString(en_US, tz, yesterday2, now, MINUTE_IN_MILLIS,
+ WEEK_IN_MILLIS, 0));
+
+ // Aug 31, 2011, 10:24 AM
+ Calendar twoDaysAgoCalendar1 = Calendar.getInstance(tz, en_US);
+ twoDaysAgoCalendar1.set(2011, Calendar.AUGUST, 31, 10, 24, 0);
+ long twoDaysAgo1 = twoDaysAgoCalendar1.getTimeInMillis();
+ assertEquals("2 days ago, 10:24 AM",
+ getRelativeDateTimeString(en_US, tz, twoDaysAgo1, now, MINUTE_IN_MILLIS,
+ WEEK_IN_MILLIS, 0));
+
+ // Aug 31, 2011, 10:22 AM
+ Calendar twoDaysAgoCalendar2 = Calendar.getInstance(tz, en_US);
+ twoDaysAgoCalendar2.set(2011, Calendar.AUGUST, 31, 10, 22, 0);
+ long twoDaysAgo2 = twoDaysAgoCalendar2.getTimeInMillis();
+ assertEquals("2 days ago, 10:22 AM",
+ getRelativeDateTimeString(en_US, tz, twoDaysAgo2, now, MINUTE_IN_MILLIS,
+ WEEK_IN_MILLIS, 0));
+
+ // Sep 3, 2011, 10:22 AM
+ Calendar tomorrowCalendar1 = Calendar.getInstance(tz, en_US);
+ tomorrowCalendar1.set(2011, Calendar.SEPTEMBER, 3, 10, 22, 0);
+ long tomorrow1 = tomorrowCalendar1.getTimeInMillis();
+ assertEquals("Tomorrow, 10:22 AM",
+ getRelativeDateTimeString(en_US, tz, tomorrow1, now, MINUTE_IN_MILLIS,
+ WEEK_IN_MILLIS, 0));
+
+ // Sep 3, 2011, 10:24 AM
+ Calendar tomorrowCalendar2 = Calendar.getInstance(tz, en_US);
+ tomorrowCalendar2.set(2011, Calendar.SEPTEMBER, 3, 10, 24, 0);
+ long tomorrow2 = tomorrowCalendar2.getTimeInMillis();
+ assertEquals("Tomorrow, 10:24 AM",
+ getRelativeDateTimeString(en_US, tz, tomorrow2, now, MINUTE_IN_MILLIS,
+ WEEK_IN_MILLIS, 0));
+
+ // Sep 4, 2011, 10:22 AM
+ Calendar twoDaysLaterCalendar1 = Calendar.getInstance(tz, en_US);
+ twoDaysLaterCalendar1.set(2011, Calendar.SEPTEMBER, 4, 10, 22, 0);
+ long twoDaysLater1 = twoDaysLaterCalendar1.getTimeInMillis();
+ assertEquals("In 2 days, 10:22 AM",
+ getRelativeDateTimeString(en_US, tz, twoDaysLater1, now, MINUTE_IN_MILLIS,
+ WEEK_IN_MILLIS, 0));
+
+ // Sep 4, 2011, 10:24 AM
+ Calendar twoDaysLaterCalendar2 = Calendar.getInstance(tz, en_US);
+ twoDaysLaterCalendar2.set(2011, Calendar.SEPTEMBER, 4, 10, 24, 0);
+ long twoDaysLater2 = twoDaysLaterCalendar2.getTimeInMillis();
+ assertEquals("In 2 days, 10:24 AM",
+ getRelativeDateTimeString(en_US, tz, twoDaysLater2, now, MINUTE_IN_MILLIS,
+ WEEK_IN_MILLIS, 0));
+ }
+
+ // b/19822016: show / hide the year based on the dates in the arguments.
+ @Test
+ public void test_bug19822016() throws Exception {
+ Locale en_US = new Locale("en", "US");
+ TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
+ Calendar cal = Calendar.getInstance(tz, en_US);
+ // Feb 5, 2012 at 10:50 PST
+ cal.set(2012, Calendar.FEBRUARY, 5, 10, 50, 0);
+ long base = cal.getTimeInMillis();
+
+ assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz,
+ base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0));
+ assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
+ assertEquals("11/27/2011, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
+
+ assertEquals("January 6", getRelativeTimeSpanString(en_US, tz,
+ base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0));
+ assertEquals("January 6", getRelativeTimeSpanString(en_US, tz,
+ base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR));
+ assertEquals("January 6, 2012", getRelativeTimeSpanString(en_US, tz,
+ base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR));
+ assertEquals("December 7, 2011", getRelativeTimeSpanString(en_US, tz,
+ base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0));
+ assertEquals("December 7, 2011", getRelativeTimeSpanString(en_US, tz,
+ base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR));
+ assertEquals("December 7", getRelativeTimeSpanString(en_US, tz,
+ base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR));
+
+ // Feb 5, 2018 at 10:50 PST
+ cal.set(2018, Calendar.FEBRUARY, 5, 10, 50, 0);
+ base = cal.getTimeInMillis();
+ assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz,
+ base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0));
+ assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
+ assertEquals("11/27/2017, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
+
+ assertEquals("January 6", getRelativeTimeSpanString(en_US, tz,
+ base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0));
+ assertEquals("January 6", getRelativeTimeSpanString(en_US, tz,
+ base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR));
+ assertEquals("January 6, 2018", getRelativeTimeSpanString(en_US, tz,
+ base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR));
+ assertEquals("December 7, 2017", getRelativeTimeSpanString(en_US, tz,
+ base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0));
+ assertEquals("December 7, 2017", getRelativeTimeSpanString(en_US, tz,
+ base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR));
+ assertEquals("December 7", getRelativeTimeSpanString(en_US, tz,
+ base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR));
+ }
+
+ // Check for missing ICU data. http://b/25821045
+ @Test
+ public void test_bug25821045() {
+ final TimeZone tz = TimeZone.getDefault();
+ final long now = System.currentTimeMillis();
+ final long time = now + 1000;
+ final int minResolution = 1000 * 60;
+ final int transitionResolution = minResolution;
+ final int flags = FORMAT_ABBREV_RELATIVE;
+ // Exercise all available locales, forcing the ICU implementation to pre-cache the data.
+ // This
+ // highlights data issues. It can take a while.
+ for (Locale locale : Locale.getAvailableLocales()) {
+ // In (e.g.) ICU56 an exception is thrown on the first use for a locale if required
+ // data for
+ // the "other" plural is missing. It doesn't matter what is actually formatted.
+ try {
+ RelativeDateTimeFormatter.getRelativeDateTimeString(
+ locale, tz, time, now, minResolution, transitionResolution, flags);
+ } catch (IllegalStateException e) {
+ fail("Failed to format for " + locale);
+ }
+ }
+ }
+
+ // Check for ICU data lookup fallback failure. http://b/25883157
+ @Test
+ public void test_bug25883157() {
+ final Locale locale = new Locale("en", "GB");
+ final TimeZone tz = TimeZone.getTimeZone("GMT");
+
+ final Calendar cal = Calendar.getInstance(tz, locale);
+ cal.set(2015, Calendar.JUNE, 19, 12, 0, 0);
+
+ final long base = cal.getTimeInMillis();
+ final long time = base + 2 * WEEK_IN_MILLIS;
+
+ assertEquals("In 2 wk", getRelativeTimeSpanString(
+ locale, tz, time, base, WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+ }
+
+ // http://b/63745717
+ @Test
+ public void test_combineDateAndTime_apostrophe() {
+ final Locale locale = new Locale("fr");
+ android.icu.text.RelativeDateTimeFormatter icuFormatter =
+ android.icu.text.RelativeDateTimeFormatter.getInstance(locale);
+ assertEquals("D à T", icuFormatter.combineDateAndTime("D", "T"));
+ // Ensure single quote ' and curly braces {} are not interpreted in input values.
+ assertEquals("D'x' à T{0}", icuFormatter.combineDateAndTime("D'x'", "T{0}"));
+ }
+}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 702f2fa..c36f106 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -691,18 +691,57 @@
@Test
public void testRequestedState() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+
+ // The modified state can be controlled when we have control.
mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
mController.hide(statusBars());
assertFalse(mTestHost.getModifiedState().peekSource(ITYPE_STATUS_BAR).isVisible());
- mController.onControlsChanged(new InsetsSourceControl[0]);
+
+ // The modified state won't be changed while losing control.
+ mController.onControlsChanged(null /* activeControls */);
assertFalse(mTestHost.getModifiedState().peekSource(ITYPE_STATUS_BAR).isVisible());
+
+ // The modified state won't be changed while state changed while we don't have control.
InsetsState newState = new InsetsState(mController.getState(), true /* copySource */);
mController.onStateChanged(newState);
assertFalse(mTestHost.getModifiedState().peekSource(ITYPE_STATUS_BAR).isVisible());
+
+ // The modified state won't be changed while controlling an insets without having the
+ // control.
mController.show(statusBars());
assertFalse(mTestHost.getModifiedState().peekSource(ITYPE_STATUS_BAR).isVisible());
+
+ // The modified state can be updated while gaining control.
mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
assertTrue(mTestHost.getModifiedState().peekSource(ITYPE_STATUS_BAR).isVisible());
+
+ // The modified state can still be updated if the local state and the requested state
+ // are the same.
+ mController.onControlsChanged(null /* activeControls */);
+ mController.hide(statusBars());
+ newState = new InsetsState(mController.getState(), true /* copySource */);
+ newState.getSource(ITYPE_STATUS_BAR).setVisible(false);
+ mController.onStateChanged(newState);
+ mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
+ assertFalse(mTestHost.getModifiedState().peekSource(ITYPE_STATUS_BAR).isVisible());
+
+ // The modified state will always be updated while receiving IME control if IME is
+ // requested visible.
+ mController.getSourceConsumer(ITYPE_IME).show(false /* fromIme */);
+ newState = new InsetsState(mController.getState(), true /* copySource */);
+ newState.getSource(ITYPE_IME).setVisible(true);
+ newState.getSource(ITYPE_IME).setFrame(1, 2, 3, 4);
+ mController.onStateChanged(newState);
+ mController.onControlsChanged(createSingletonControl(ITYPE_IME));
+ assertEquals(newState.getSource(ITYPE_IME),
+ mTestHost.getModifiedState().peekSource(ITYPE_IME));
+ newState = new InsetsState(mController.getState(), true /* copySource */);
+ newState.getSource(ITYPE_IME).setVisible(true);
+ newState.getSource(ITYPE_IME).setFrame(5, 6, 7, 8);
+ mController.onStateChanged(newState);
+ mController.onControlsChanged(createSingletonControl(ITYPE_IME));
+ assertEquals(newState.getSource(ITYPE_IME),
+ mTestHost.getModifiedState().peekSource(ITYPE_IME));
});
}
diff --git a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
index 76aa93f..edf473e 100644
--- a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
@@ -16,27 +16,25 @@
package com.android.internal.util;
-import java.util.Collection;
-import java.util.Iterator;
-
import android.os.Debug;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.os.test.TestLooper;
-
-import android.test.suitebuilder.annotation.Suppress;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.internal.util.StateMachine.LogRec;
-
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
+import com.android.internal.util.StateMachine.LogRec;
+
import junit.framework.TestCase;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.Iterator;
+
/**
* Test for StateMachine.
*/
@@ -2013,4 +2011,12 @@
private static void tloge(String s) {
Log.e(TAG, s);
}
+
+ public void testDumpDoesNotThrowNpeAfterQuit() {
+ final Hsm1 sm = Hsm1.makeHsm1();
+ sm.quitNow();
+ final StringWriter stringWriter = new StringWriter();
+ final PrintWriter printWriter = new PrintWriter(stringWriter);
+ sm.dump(null, printWriter, new String[0]);
+ }
}
diff --git a/data/etc/preinstalled-packages-platform-overlays.xml b/data/etc/preinstalled-packages-platform-overlays.xml
index ecd7d40..84c1897a2 100644
--- a/data/etc/preinstalled-packages-platform-overlays.xml
+++ b/data/etc/preinstalled-packages-platform-overlays.xml
@@ -19,32 +19,250 @@
<config>
<install-in-user-type package="com.android.internal.display.cutout.emulation.corner">
<install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
</install-in-user-type>
<install-in-user-type package="com.android.internal.display.cutout.emulation.double">
<install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.internal.display.cutout.emulation.hole">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
</install-in-user-type>
<install-in-user-type package="com.android.internal.display.cutout.emulation.tall">
<install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
</install-in-user-type>
- <install-in-user-type package="com.android.internal.systemui.navbar.gestural">
+ <install-in-user-type package="com.android.internal.display.cutout.emulation.waterfall">
<install-in user-type="FULL" />
- </install-in-user-type>
- <install-in-user-type package="com.android.internal.systemui.navbar.gestural_extra_wide_back">
- <install-in user-type="FULL" />
- </install-in-user-type>
- <install-in-user-type package="com.android.internal.systemui.navbar.gestural_narrow_back">
- <install-in user-type="FULL" />
- </install-in-user-type>
- <install-in-user-type package="com.android.internal.systemui.navbar.gestural_wide_back">
- <install-in user-type="FULL" />
- </install-in-user-type>
- <install-in-user-type package="com.android.internal.systemui.navbar.threebutton">
- <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
</install-in-user-type>
<install-in-user-type package="com.android.internal.systemui.navbar.twobutton">
<install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.internal.systemui.navbar.threebutton">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.internal.systemui.navbar.gestural">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.internal.systemui.navbar.gestural_extra_wide_back">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.internal.systemui.navbar.gestural_narrow_back">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.internal.systemui.navbar.gestural_wide_back">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
</install-in-user-type>
<install-in-user-type package="com.android.internal.systemui.onehanded.gestural">
<install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.amethyst">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.aquamarine">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.black">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.carbon">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.cinnamon">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.green">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.ocean">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.orchid">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.palette">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.purple">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.sand">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.space">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.color.tangerine">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.font.notoserifsource">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.circular.android">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.circular.launcher">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.circular.settings">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.circular.systemui">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.circular.themepicker">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.filled.android">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.filled.launcher">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.filled.settings">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.filled.systemui">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.filled.themepicker">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.kai.android">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.kai.launcher">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.kai.settings">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.kai.systemui">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.kai.themepicker">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.rounded.android">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.rounded.launcher">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.rounded.settings">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.rounded.systemui">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.rounded.themepicker">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.sam.android">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.sam.launcher">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.sam.settings">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.sam.systemui">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.sam.themepicker">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.victor.android">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.victor.launcher">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.victor.settings">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.victor.systemui">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon_pack.victor.themepicker">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon.pebble">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon.roundedrect">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon.squircle">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon.taperedrect">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon.teardrop">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
+ <install-in-user-type package="com.android.theme.icon.vessel">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
</install-in-user-type>
</config>
diff --git a/diff b/diff
new file mode 100644
index 0000000..5c75d88
--- /dev/null
+++ b/diff
@@ -0,0 +1,25 @@
+diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+index fc43882..832dc91 100644
+--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
++++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+@@ -67,6 +67,7 @@ import java.util.Arrays;
+ import java.util.HashSet;
+ import java.util.List;
+ import java.util.Set;
++import java.util.NoSuchElementException;
+
+ /**
+ * This class represents an accessibility client - either an AccessibilityService or a UiAutomation.
+@@ -978,7 +979,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
+ /* ignore */
+ }
+ if (mService != null) {
+- mService.unlinkToDeath(this, 0);
++ try {
++ mService.unlinkToDeath(this, 0);
++ }catch(NoSuchElementException e) {
++ Slog.e(LOG_TAG, "Failed unregistering death link");
++ }
+ mService = null;
+ }
+
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index f502fc3..14a297f 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -39,29 +39,20 @@
glScissor(clip.fLeft, y, clip.width(), height);
}
-static bool GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) {
+static void GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) {
GrRenderTargetContext* renderTargetContext =
canvas->internal_private_accessTopLayerRenderTargetContext();
- if (!renderTargetContext) {
- ALOGW("Unable to extract renderTarget info from canvas; aborting GLFunctor draw");
- return false;
- }
+ LOG_ALWAYS_FATAL_IF(!renderTargetContext, "Failed to retrieve GrRenderTargetContext");
GrRenderTarget* renderTarget = renderTargetContext->accessRenderTarget();
- if (!renderTarget) {
- ALOGW("Unable to extract renderTarget info from canvas; aborting GLFunctor draw");
- return false;
- }
+ LOG_ALWAYS_FATAL_IF(!renderTarget, "accessRenderTarget failed");
GrGLFramebufferInfo fboInfo;
- if (!renderTarget->getBackendRenderTarget().getGLFramebufferInfo(&fboInfo)) {
- ALOGW("Unable to extract renderTarget info from canvas; aborting GLFunctor draw");
- return false;
- }
+ LOG_ALWAYS_FATAL_IF(!renderTarget->getBackendRenderTarget().getGLFramebufferInfo(&fboInfo),
+ "getGLFrameBufferInfo failed");
*outFboID = fboInfo.fFBOID;
*outFboSize = SkISize::Make(renderTargetContext->width(), renderTargetContext->height());
- return true;
}
void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
@@ -76,11 +67,12 @@
return;
}
+ // flush will create a GrRenderTarget if not already present.
+ canvas->flush();
+
GLuint fboID = 0;
SkISize fboSize;
- if (!GetFboDetails(canvas, &fboID, &fboSize)) {
- return;
- }
+ GetFboDetails(canvas, &fboID, &fboSize);
SkIRect surfaceBounds = canvas->internal_private_getTopLayerBounds();
SkIRect clipBounds = canvas->getDeviceClipBounds();
@@ -134,7 +126,6 @@
// ensure that the framebuffer that the webview will render into is bound before we clear
// the stencil and/or draw the functor.
- canvas->flush();
glViewport(0, 0, info.width, info.height);
glBindFramebuffer(GL_FRAMEBUFFER, fboID);
diff --git a/location/java/android/location/package.html b/location/java/android/location/package.html
index 2355e72..20c5c54 100644
--- a/location/java/android/location/package.html
+++ b/location/java/android/location/package.html
@@ -6,7 +6,7 @@
<p class="warning">
<strong>This API is not the recommended method for accessing Android location.</strong><br>
The
-<a href="{@docRoot}reference/com/google/android/gms/location/package-summary.html">Google Location Services API</a>,
+<a href="https://developers.google.com/android/reference/com/google/android/gms/location/package-summary">Google Location Services API</a>,
part of Google Play services, is the preferred way to add location-awareness to
your app. It offers a simpler API, higher accuracy, low-power geofencing, and
more. If you are currently using the android.location API, you are strongly
diff --git a/location/java/com/android/internal/location/ILocationProviderManager.aidl b/location/java/com/android/internal/location/ILocationProviderManager.aidl
index 4036d63..2a6cef8 100644
--- a/location/java/com/android/internal/location/ILocationProviderManager.aidl
+++ b/location/java/com/android/internal/location/ILocationProviderManager.aidl
@@ -25,15 +25,8 @@
* @hide
*/
interface ILocationProviderManager {
-
- void onSetAttributionTag(String attributionTag);
-
- @UnsupportedAppUsage
+ void onSetIdentity(@nullable String packageName, @nullable String attributionTag);
void onSetAllowed(boolean allowed);
-
- @UnsupportedAppUsage
void onSetProperties(in ProviderProperties properties);
-
- @UnsupportedAppUsage
void onReportLocation(in Location location);
}
diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java
index 25b4090..7a3a4b2 100644
--- a/location/lib/java/com/android/location/provider/LocationProviderBase.java
+++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java
@@ -79,7 +79,8 @@
public static final String FUSED_PROVIDER = LocationManager.FUSED_PROVIDER;
final String mTag;
- final String mAttributionTag;
+ @Nullable final String mPackageName;
+ @Nullable final String mAttributionTag;
final IBinder mBinder;
/**
@@ -93,8 +94,7 @@
protected final ILocationManager mLocationManager;
// write locked on mBinder, read lock is optional depending on atomicity requirements
- @Nullable
- volatile ILocationProviderManager mManager;
+ @Nullable volatile ILocationProviderManager mManager;
volatile ProviderProperties mProperties;
volatile boolean mAllowed;
@@ -116,6 +116,7 @@
public LocationProviderBase(Context context, String tag,
ProviderPropertiesUnbundled properties) {
mTag = tag;
+ mPackageName = context != null ? context.getPackageName() : null;
mAttributionTag = context != null ? context.getAttributionTag() : null;
mBinder = new Service();
@@ -332,8 +333,8 @@
public void setLocationProviderManager(ILocationProviderManager manager) {
synchronized (mBinder) {
try {
- if (mAttributionTag != null) {
- manager.onSetAttributionTag(mAttributionTag);
+ if (mPackageName != null || mAttributionTag != null) {
+ manager.onSetIdentity(mPackageName, mAttributionTag);
}
manager.onSetProperties(mProperties);
manager.onSetAllowed(mAllowed);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index ebd7658..942f908 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -167,34 +167,6 @@
}
}
- @SmallTest
- public void testConnectLegacy() throws Exception {
- final int CAMERA_HAL_API_VERSION_1_0 = 0x100;
- for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
- ICamera cameraUser = null;
- ICameraClient dummyCallbacks = new DummyCameraClient();
-
- String clientPackageName = getContext().getPackageName();
-
- try {
- cameraUser = mUtils.getCameraService()
- .connectLegacy(dummyCallbacks, cameraId, CAMERA_HAL_API_VERSION_1_0,
- clientPackageName,
- ICameraService.USE_CALLING_UID);
- assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
-
- Log.v(TAG, String.format("Camera %s connected as HAL1 legacy device", cameraId));
- } catch (RuntimeException e) {
- // Not all camera device support openLegacy.
- Log.i(TAG, "Unable to open camera as HAL1 legacy camera device " + e);
- } finally {
- if (cameraUser != null) {
- cameraUser.disconnect();
- }
- }
- }
- }
-
static class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
/*
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java
deleted file mode 100644
index 14bbe44..0000000
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2013 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.mediaframeworktest.unit;
-
-import android.hardware.Camera;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-
-/**
- * <pre>
- * adb shell am instrument \
- * -e class 'com.android.mediaframeworktest.unit.CameraOpenTest' \
- * -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner
- * </pre>
- */
-public class CameraOpenTest extends junit.framework.TestCase {
- private static String TAG = "CameraOpenTest";
-
- private Camera mCamera;
-
- /**
- * Test @hide android.hardware.Camera#openLegacy API that cannot be tested in CTS.
- */
- @SmallTest
- public void testOpenLegacy() {
- int nCameras = Camera.getNumberOfCameras();
- for (int id = 0; id < nCameras; id++) {
- try {
- mCamera.openLegacy(id, Camera.CAMERA_HAL_API_VERSION_1_0);
- } catch (RuntimeException e) {
- Log.i(TAG, "Unable to open camera as HAL1 legacy camera device " + e);
- } finally {
- if (mCamera != null) {
- mCamera.release();
- }
- }
- }
- }
-}
diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java
index ee06720..de5ed54 100644
--- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java
+++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodingBenchmark.java
@@ -62,7 +62,7 @@
// TODO(hkuang): Change this to query from MediaCodecInfo.CodecCapabilities for different
// resolution.
private static final int MINIMUM_TRANSCODING_FPS = 80;
- private static final int LOOP_COUNT = 10;
+ private static final int LOOP_COUNT = 3;
// Default Setting for transcoding to H.264.
private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
private static final int BIT_RATE = 20000000; // 20Mbps
@@ -109,6 +109,7 @@
MediaTranscodingTestUtil.VideoFileInfo info =
MediaTranscodingTestUtil.extractVideoFileInfo(mContext, getUri(sourceFileName));
+
int timeoutSeconds = calMaxTranscodingWaitTimeSeconds(info.mNumVideoFrames,
MINIMUM_TRANSCODING_FPS);
Log.d(TAG, "Start Transcoding " + info.toString() + " " + timeoutSeconds);
@@ -151,8 +152,9 @@
// Calculate the maximum wait time based on minimum transcoding throughput and frame number.
private int calMaxTranscodingWaitTimeSeconds(int numberFrames, int minTranscodingFps) {
- int waitTimeSeconds = numberFrames / minTranscodingFps;
- // If waitTimeSeconds is 0, wait for 1 seconds at least.
+ float waitTime = (float) numberFrames / (float) minTranscodingFps;
+ // If waitTimeSeconds is 0, wait for 1 second at least.
+ int waitTimeSeconds = (int) Math.ceil(waitTime);
return waitTimeSeconds == 0 ? 1 : waitTimeSeconds;
}
@@ -171,6 +173,9 @@
transcode(testVideoName, transcodedVideoName);
}
+ // TODO(hkuang): Enable this after b/160268606 is fixed. Transcoding video with audio takes way
+ // more long time that leads to timeout failure.
+ /*
@Test
public void testBenchmarkingAVCToAVCWith66FramesWithAudio() throws Exception {
String videoNameWithoutExtension = "video_1920x1080_66frame_h264_22Mbps_30fps_aac";
@@ -178,5 +183,105 @@
String transcodedVideoName = videoNameWithoutExtension + "_transcode.mp4";
transcode(testVideoName, transcodedVideoName);
+ }*/
+
+ @Test
+ public void testBenchmarkingAVCToAVCWith361FramesWithoutAudio() throws Exception {
+ String videoNameWithoutExtension = "video_1920x1080_361frame_h264_22Mbps_30fps";
+ String testVideoName = videoNameWithoutExtension + ".mp4";
+ String transcodedVideoName = videoNameWithoutExtension + "_transcode.mp4";
+
+ transcode(testVideoName, transcodedVideoName);
}
+
+ // TODO(hkuang): Enable this after b/160268606 is fixed. Transcoding video with audio takes way
+ // more long time that leads to timeout failure.
+ /*@Test
+ public void testBenchmarkingAVCToAVCWith361FramesWithAudio() throws Exception {
+ String videoNameWithoutExtension = "video_1920x1080_361frame_h264_22Mbps_30fps_aac";
+ String testVideoName = videoNameWithoutExtension + ".mp4";
+ String transcodedVideoName = videoNameWithoutExtension + "_transcode.mp4";
+
+ transcode(testVideoName, transcodedVideoName);
+ }*/
+
+ @Test
+ public void testBenchmarkingAVCToAVCWith943FramesWithoutAudio() throws Exception {
+ String videoNameWithoutExtension = "video_1920x1080_943frame_h264_22Mbps_30fps";
+ String testVideoName = videoNameWithoutExtension + ".mp4";
+ String transcodedVideoName = videoNameWithoutExtension + "_transcode.mp4";
+
+ transcode(testVideoName, transcodedVideoName);
+ }
+
+ // TODO(hkuang): Enable this after b/160268606 is fixed. Transcoding video with audio takes way
+ // more long time that leads to timeout failure.
+ /* @Test
+ public void testBenchmarkingAVCToAVCWith943FramesWithAudio() throws Exception {
+ String videoNameWithoutExtension = "video_1920x1080_943frame_h264_22Mbps_30fps_aac";
+ String testVideoName = videoNameWithoutExtension + ".mp4";
+ String transcodedVideoName = videoNameWithoutExtension + "_transcode.mp4";
+
+ transcode(testVideoName, transcodedVideoName);
+ }*/
+
+ @Test
+ public void testBenchmarkingAVCToAVCWith1822FramesWithoutAudio() throws Exception {
+ String videoNameWithoutExtension = "video_1920x1080_1822frame_h264_22Mbps_30fps";
+ String testVideoName = videoNameWithoutExtension + ".mp4";
+ String transcodedVideoName = videoNameWithoutExtension + "_transcode.mp4";
+
+ transcode(testVideoName, transcodedVideoName);
+ }
+
+ // TODO(hkuang): Enable this after b/160268606 is fixed. Transcoding video with audio takes way
+ // more long time that leads to timeout failure.
+ /*@Test
+ public void testBenchmarkingAVCToAVCWith1822FramesWithAudio() throws Exception {
+ String videoNameWithoutExtension = "video_1920x1080_1822frame_h264_22Mbps_30fps_aac";
+ String testVideoName = videoNameWithoutExtension + ".mp4";
+ String transcodedVideoName = videoNameWithoutExtension + "_transcode.mp4";
+
+ transcode(testVideoName, transcodedVideoName);
+ }*/
+
+ @Test
+ public void testBenchmarkingAVCToAVCWith3648FramesWithoutAudio() throws Exception {
+ String videoNameWithoutExtension = "video_1920x1080_3648frame_h264_22Mbps_30fps";
+ String testVideoName = videoNameWithoutExtension + ".mp4";
+ String transcodedVideoName = videoNameWithoutExtension + "_transcode.mp4";
+
+ transcode(testVideoName, transcodedVideoName);
+ }
+
+ // TODO(hkuang): Enable this after b/160268606 is fixed. Transcoding video with audio takes way
+ // more long time that leads to timeout failure.
+ /*@Test
+ public void testBenchmarkingAVCToAVCWith3648FramesWithAudio() throws Exception {
+ String videoNameWithoutExtension = "video_1920x1080_3648frame_h264_22Mbps_30fps_aac";
+ String testVideoName = videoNameWithoutExtension + ".mp4";
+ String transcodedVideoName = videoNameWithoutExtension + "_transcode.mp4";
+
+ transcode(testVideoName, transcodedVideoName);
+ }*/
+
+ @Test
+ public void testBenchmarkingAVCToAVCWith11042FramesWithoutAudio() throws Exception {
+ String videoNameWithoutExtension = "video_1920x1080_11042frame_h264_22Mbps_30fps";
+ String testVideoName = videoNameWithoutExtension + ".mp4";
+ String transcodedVideoName = videoNameWithoutExtension + "_transcode.mp4";
+
+ transcode(testVideoName, transcodedVideoName);
+ }
+
+ // TODO(hkuang): Enable this after b/160268606 is fixed. Transcoding video with audio takes way
+ // more long time that leads to timeout failure.
+ /*@Test
+ public void testBenchmarkingAVCToAVCWith11042FramesWithAudio() throws Exception {
+ String videoNameWithoutExtension = "video_1920x1080_11042frame_h264_22Mbps_30fps_aac";
+ String testVideoName = videoNameWithoutExtension + ".mp4";
+ String transcodedVideoName = videoNameWithoutExtension + "_transcode.mp4";
+
+ transcode(testVideoName, transcodedVideoName);
+ }*/
}
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index 34f16f8..c14f23f 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -45194,7 +45194,7 @@
public abstract class CellLocation {
ctor public CellLocation();
method public static android.telephony.CellLocation getEmpty();
- method public static void requestLocationUpdate();
+ method @Deprecated public static void requestLocationUpdate();
}
public abstract class CellSignalStrength {
@@ -47256,6 +47256,7 @@
}
public static class MmTelFeature.MmTelCapabilities {
+ method public final boolean isCapable(int);
field public static final int CAPABILITY_TYPE_SMS = 8; // 0x8
field public static final int CAPABILITY_TYPE_UT = 4; // 0x4
field public static final int CAPABILITY_TYPE_VIDEO = 2; // 0x2
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index b693b0e..b41ab93 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -11118,7 +11118,6 @@
ctor @Deprecated public MmTelFeature.MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities);
ctor public MmTelFeature.MmTelCapabilities(int);
method public final void addCapabilities(int);
- method public final boolean isCapable(int);
method public final void removeCapabilities(int);
}
diff --git a/non-updatable-api/system-removed.txt b/non-updatable-api/system-removed.txt
index ab4c6d1..0c02c43 100644
--- a/non-updatable-api/system-removed.txt
+++ b/non-updatable-api/system-removed.txt
@@ -165,11 +165,6 @@
package android.telephony {
- public final class PreciseDataConnectionState implements android.os.Parcelable {
- method @Deprecated @Nullable public android.net.LinkProperties getDataConnectionLinkProperties();
- method @Deprecated public int getDataConnectionNetworkType();
- }
-
public class TelephonyManager {
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void answerRingingCall();
method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public boolean endCall();
diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
index ed7f3df..8efbad6 100644
--- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
@@ -199,7 +199,7 @@
}
@Override
- public void onSetAttributionTag(String attributionTag) {
+ public void onSetIdentity(String packageName, String attributionTag) {
}
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 376c2e7..0042321 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -37,7 +37,7 @@
<string name="wifi_no_internet" msgid="1774198889176926299">"Ezin da konektatu Internetera"</string>
<string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> aplikazioak gorde du"</string>
<string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s bidez automatikoki konektatuta"</string>
- <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatikoki konektatuta sareen balorazioen hornitzailearen bidez"</string>
+ <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatikoki konektatuta sare-balorazioen hornitzailearen bidez"</string>
<string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s bidez konektatuta"</string>
<string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> bidez konektatuta"</string>
<string name="available_via_passpoint" msgid="1716000261192603682">"%1$s bidez erabilgarri"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 8968340..c5e66be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -1117,14 +1117,16 @@
* Returns the display title for the AccessPoint, such as for an AccessPointPreference's title.
*/
public String getTitle() {
- if (isPasspoint()) {
+ if (isPasspoint() && !TextUtils.isEmpty(mConfig.providerFriendlyName)) {
return mConfig.providerFriendlyName;
- } else if (isPasspointConfig()) {
+ } else if (isPasspointConfig() && !TextUtils.isEmpty(mProviderFriendlyName)) {
return mProviderFriendlyName;
- } else if (isOsuProvider()) {
+ } else if (isOsuProvider() && !TextUtils.isEmpty(mOsuProvider.getFriendlyName())) {
return mOsuProvider.getFriendlyName();
- } else {
+ } else if (!TextUtils.isEmpty(getSsidStr())) {
return getSsidStr();
+ } else {
+ return "";
}
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index bcabec8..46ecbd4 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -148,6 +148,17 @@
}
@Test
+ public void testCompareTo_GivesNull() {
+ WifiConfiguration spyConfig = spy(new WifiConfiguration());
+
+ when(spyConfig.isPasspoint()).thenReturn(true);
+ spyConfig.providerFriendlyName = null;
+ AccessPoint passpointAp = new AccessPoint(mContext, spyConfig);
+
+ assertThat(passpointAp.getTitle()).isEqualTo("");
+ }
+
+ @Test
public void testCompareTo_GivesActiveBeforeInactive() {
AccessPoint activeAp = new TestAccessPointBuilder(mContext).setActive(true).build();
AccessPoint inactiveAp = new TestAccessPointBuilder(mContext).setActive(false).build();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 94d95f06..68f162e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -27,7 +27,9 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -36,6 +38,7 @@
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
+import android.media.session.MediaSessionManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
@@ -68,6 +71,8 @@
private LocalBluetoothManager mLocalBluetoothManager;
@Mock
private MediaManager.MediaDeviceCallback mCallback;
+ @Mock
+ private MediaSessionManager mMediaSessionManager;
private InfoMediaManager mInfoMediaManager;
private Context mContext;
@@ -76,8 +81,10 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = spy(RuntimeEnvironment.application);
+ doReturn(mMediaSessionManager).when(mContext).getSystemService(
+ Context.MEDIA_SESSION_SERVICE);
mInfoMediaManager =
new InfoMediaManager(mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager);
mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 780c19b..d52e247 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -28,15 +28,15 @@
<string name="battery_low_percent_format" msgid="4276661262843170964">"僅剩 <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="battery_low_percent_format_hybrid" msgid="3985614339605686167">"電力剩餘 <xliff:g id="PERCENTAGE">%1$s</xliff:g>,根據你的使用情形,剩餘時間大約還有 <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="battery_low_percent_format_hybrid_short" msgid="5917433188456218857">"電力剩餘 <xliff:g id="PERCENTAGE">%1$s</xliff:g>,剩餘時間大約還有 <xliff:g id="TIME">%2$s</xliff:g>"</string>
- <string name="battery_low_percent_format_saver_started" msgid="4968468824040940688">"僅剩 <xliff:g id="PERCENTAGE">%s</xliff:g>。節約耗電量模式已開啟。"</string>
+ <string name="battery_low_percent_format_saver_started" msgid="4968468824040940688">"僅剩 <xliff:g id="PERCENTAGE">%s</xliff:g>。省電模式已開啟。"</string>
<string name="invalid_charger" msgid="4370074072117767416">"無法透過 USB 充電,請使用裝置隨附的充電器。"</string>
<string name="invalid_charger_title" msgid="938685362320735167">"無法透過 USB 充電"</string>
<string name="invalid_charger_text" msgid="2339310107232691577">"使用裝置隨附的充電器"</string>
<string name="battery_low_why" msgid="2056750982959359863">"設定"</string>
- <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"要開啟節約耗電量模式嗎?"</string>
- <string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"關於節約耗電量功能"</string>
+ <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"要開啟省電模式嗎?"</string>
+ <string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"關於省電模式"</string>
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"開啟"</string>
- <string name="battery_saver_start_action" msgid="4553256017945469937">"開啟節約耗電量模式"</string>
+ <string name="battery_saver_start_action" msgid="4553256017945469937">"開啟省電模式"</string>
<string name="status_bar_settings_settings_button" msgid="534331565185171556">"設定"</string>
<string name="status_bar_settings_wifi_button" msgid="7243072479837270946">"Wi-Fi"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"自動旋轉螢幕"</string>
@@ -421,7 +421,7 @@
<string name="quick_settings_night_secondary_label_on_at" msgid="3584738542293528235">"<xliff:g id="TIME">%s</xliff:g> 開啟"</string>
<string name="quick_settings_secondary_label_until" msgid="1883981263191927372">"<xliff:g id="TIME">%s</xliff:g> 關閉"</string>
<string name="quick_settings_ui_mode_night_label" msgid="1398928270610780470">"深色主題"</string>
- <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"節約耗電量"</string>
+ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"省電模式"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"於日落時開啟"</string>
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"於日出時關閉"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"開啟時間:<xliff:g id="TIME">%s</xliff:g>"</string>
@@ -499,9 +499,9 @@
<string name="user_remove_user_title" msgid="9124124694835811874">"要移除使用者嗎?"</string>
<string name="user_remove_user_message" msgid="6702834122128031833">"系統將刪除這個使用者的所有應用程式和資料。"</string>
<string name="user_remove_user_remove" msgid="8387386066949061256">"移除"</string>
- <string name="battery_saver_notification_title" msgid="8419266546034372562">"節約耗電量模式已開啟"</string>
+ <string name="battery_saver_notification_title" msgid="8419266546034372562">"省電模式已開啟"</string>
<string name="battery_saver_notification_text" msgid="2617841636449016951">"降低效能並限制背景數據傳輸"</string>
- <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"關閉節約耗電量模式"</string>
+ <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"關閉省電模式"</string>
<string name="media_projection_dialog_text" msgid="1755705274910034772">"在錄製或投放內容時,「<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>」可存取畫面上顯示的任何資訊或裝置播放的任何內容,包括密碼、付款詳情、相片、訊息和你播放的音訊。"</string>
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"在錄製或投放內容時,提供這項功能的服務可存取畫面上顯示的任何資訊或裝置播放的任何內容,包括密碼、付款詳情、相片、訊息和你播放的音訊。"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"要開始錄製或投放內容嗎?"</string>
@@ -775,8 +775,8 @@
<item quantity="one">%d 分鐘</item>
</plurals>
<string name="battery_panel_title" msgid="5931157246673665963">"電池用量"</string>
- <string name="battery_detail_charging_summary" msgid="8821202155297559706">"充電時無法使用節約耗電量模式"</string>
- <string name="battery_detail_switch_title" msgid="6940976502957380405">"節約耗電量"</string>
+ <string name="battery_detail_charging_summary" msgid="8821202155297559706">"充電時無法使用省電模式"</string>
+ <string name="battery_detail_switch_title" msgid="6940976502957380405">"省電模式"</string>
<string name="battery_detail_switch_summary" msgid="3668748557848025990">"降低效能並限制背景資料傳輸"</string>
<string name="keyboard_key_button_template" msgid="8005673627272051429">"<xliff:g id="NAME">%1$s</xliff:g> 按鈕"</string>
<string name="keyboard_key_home" msgid="3734400625170020657">"Home 鍵"</string>
@@ -988,11 +988,11 @@
<string name="slice_permission_checkbox" msgid="4242888137592298523">"允許「<xliff:g id="APP">%1$s</xliff:g>」顯示任何應用程式的區塊"</string>
<string name="slice_permission_allow" msgid="6340449521277951123">"允許"</string>
<string name="slice_permission_deny" msgid="6870256451658176895">"拒絕"</string>
- <string name="auto_saver_title" msgid="6873691178754086596">"輕觸即可排定節約耗電量模式自動開啟的情況"</string>
+ <string name="auto_saver_title" msgid="6873691178754086596">"輕觸即可排定省電模式自動開啟的情況"</string>
<string name="auto_saver_text" msgid="3214960308353838764">"在電池電量即將耗盡時開啟"</string>
<string name="no_auto_saver_action" msgid="7467924389609773835">"不用了,謝謝"</string>
- <string name="auto_saver_enabled_title" msgid="4294726198280286333">"已按照排定開啟節約耗電量模式"</string>
- <string name="auto_saver_enabled_text" msgid="7889491183116752719">"節約耗電量模式會在電量低於 <xliff:g id="PERCENTAGE">%d</xliff:g>%% 時自動開啟。"</string>
+ <string name="auto_saver_enabled_title" msgid="4294726198280286333">"已按照排定開啟省電模式"</string>
+ <string name="auto_saver_enabled_text" msgid="7889491183116752719">"省電模式會在電量低於 <xliff:g id="PERCENTAGE">%d</xliff:g>%% 時自動開啟。"</string>
<string name="open_saver_setting_action" msgid="2111461909782935190">"設定"</string>
<string name="auto_saver_okay_action" msgid="7815925750741935386">"我知道了"</string>
<string name="heap_dump_tile_name" msgid="2464189856478823046">"傾印 SysUI 記憶體快照"</string>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 8212d61..a56f6f5 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -92,6 +92,8 @@
<item type="id" name="requires_remeasuring"/>
+ <item type="id" name="secondary_home_handle" />
+
<!-- Whether the icon is from a notification for which targetSdk < L -->
<item type="id" name="icon_is_pre_L"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 82c111f..454ffc8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2422,17 +2422,26 @@
<!-- Title for notification & dialog that the user's phone last shut down because it got too hot. [CHAR LIMIT=40] -->
<string name="thermal_shutdown_title">Phone turned off due to heat</string>
- <!-- Message body for notification that user's phone last shut down because it got too hot. [CHAR LIMIT=100] -->
- <string name="thermal_shutdown_message">Your phone is now running normally</string>
- <!-- Text body for dialog alerting user that their phone last shut down because it got too hot. [CHAR LIMIT=450] -->
+ <!-- Message body for notification that user's phone last shut down because it got too hot. [CHAR LIMIT=120] -->
+ <string name="thermal_shutdown_message">Your phone is now running normally.\nTap for more info</string>
+ <!-- Text body for dialog alerting user that their phone last shut down because it got too hot. [CHAR LIMIT=500] -->
<string name="thermal_shutdown_dialog_message">Your phone was too hot, so it turned off to cool down. Your phone is now running normally.\n\nYour phone may get too hot if you:\n\t• Use resource-intensive apps (such as gaming, video, or navigation apps)\n\t• Download or upload large files\n\t• Use your phone in high temperatures</string>
+ <!-- Text help link for care instructions for overheating devices [CHAR LIMIT=40] -->
+ <string name="thermal_shutdown_dialog_help_text">See care steps</string>
+ <!-- URL for care instructions for overheating devices -->
+ <string name="thermal_shutdown_dialog_help_url" translatable="false"></string>
<!-- Title for notification (and dialog) that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=30] -->
<string name="high_temp_title">Phone is getting warm</string>
- <!-- Message body for notification that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=100] -->
- <string name="high_temp_notif_message">Some features limited while phone cools down</string>
- <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=300] -->
+ <!-- Message body for notification that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=120] -->
+ <string name="high_temp_notif_message">Some features limited while phone cools down.\nTap for more info</string>
+ <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=350] -->
<string name="high_temp_dialog_message">Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally.</string>
+ <!-- Text help link for care instructions for overheating devices [CHAR LIMIT=40] -->
+ <string name="high_temp_dialog_help_text">See care steps</string>
+ <!-- URL for care instructions for overheating devices -->
+ <string name="high_temp_dialog_help_url" translatable="false"></string>
+
<!-- Title for alarm dialog alerting user the usb adapter has reached a certain temperature that should disconnect charging cable immediately. [CHAR LIMIT=30] -->
<string name="high_temp_alarm_title">Unplug charger</string>
<!-- Text body for dialog alerting user the usb adapter has reached a certain temperature that should disconnect charging cable immediately. [CHAR LIMIT=300] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
index 82e6251..2985a61 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
@@ -46,6 +46,7 @@
public static final int FLAG_CORNER_RADIUS = 1 << 4;
public static final int FLAG_BACKGROUND_BLUR_RADIUS = 1 << 5;
public static final int FLAG_VISIBILITY = 1 << 6;
+ public static final int FLAG_RELATIVE_LAYER = 1 << 7;
private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0;
@@ -192,6 +193,8 @@
Matrix matrix;
Rect windowCrop;
int layer;
+ SurfaceControl relativeTo;
+ int relativeLayer;
boolean visible;
/**
@@ -249,6 +252,18 @@
}
/**
+ * @param relativeTo The surface that's set relative layer to.
+ * @param relativeLayer The relative layer.
+ * @return this Builder
+ */
+ public Builder withRelativeLayerTo(SurfaceControl relativeTo, int relativeLayer) {
+ this.relativeTo = relativeTo;
+ this.relativeLayer = relativeLayer;
+ flags |= FLAG_RELATIVE_LAYER;
+ return this;
+ }
+
+ /**
* @param radius the Radius for rounded corners to apply to the surface.
* @return this Builder
*/
@@ -283,7 +298,7 @@
*/
public SurfaceParams build() {
return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer,
- cornerRadius, backgroundBlurRadius, visible);
+ relativeTo, relativeLayer, cornerRadius, backgroundBlurRadius, visible);
}
}
@@ -297,21 +312,25 @@
* @param windowCrop Crop to apply, only applied if not {@code null}
*/
public SurfaceParams(SurfaceControlCompat surface, float alpha, Matrix matrix,
- Rect windowCrop, int layer, float cornerRadius) {
+ Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer,
+ float cornerRadius) {
this(surface.mSurfaceControl,
FLAG_ALL & ~(FLAG_VISIBILITY | FLAG_BACKGROUND_BLUR_RADIUS), alpha,
- matrix, windowCrop, layer, cornerRadius, 0 /* backgroundBlurRadius */, true);
+ matrix, windowCrop, layer, relativeTo, relativeLayer, cornerRadius,
+ 0 /* backgroundBlurRadius */, true);
}
private SurfaceParams(SurfaceControl surface, int flags, float alpha, Matrix matrix,
- Rect windowCrop, int layer, float cornerRadius, int backgroundBlurRadius,
- boolean visible) {
+ Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer,
+ float cornerRadius, int backgroundBlurRadius, boolean visible) {
this.flags = flags;
this.surface = surface;
this.alpha = alpha;
this.matrix = new Matrix(matrix);
this.windowCrop = windowCrop != null ? new Rect(windowCrop) : null;
this.layer = layer;
+ this.relativeTo = relativeTo;
+ this.relativeLayer = relativeLayer;
this.cornerRadius = cornerRadius;
this.backgroundBlurRadius = backgroundBlurRadius;
this.visible = visible;
@@ -327,6 +346,8 @@
public final Matrix matrix;
public final Rect windowCrop;
public final int layer;
+ public final SurfaceControl relativeTo;
+ public final int relativeLayer;
public final boolean visible;
public void applyTo(SurfaceControl.Transaction t) {
@@ -355,6 +376,9 @@
t.hide(surface);
}
}
+ if ((flags & FLAG_RELATIVE_LAYER) != 0) {
+ t.setRelativeLayer(surface, relativeTo, relativeLayer);
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index e45cfe2..71969e0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -228,6 +228,7 @@
private final Context mContext;
private final boolean mIsPrimaryUser;
+ private final boolean mIsAutomotive;
private final StatusBarStateController mStatusBarStateController;
HashMap<Integer, SimData> mSimDatas = new HashMap<>();
HashMap<Integer, ServiceState> mServiceStates = new HashMap<Integer, ServiceState>();
@@ -295,7 +296,7 @@
private int mHardwareFingerprintUnavailableRetryCount = 0;
private int mHardwareFaceUnavailableRetryCount = 0;
private static final int HAL_ERROR_RETRY_TIMEOUT = 500; // ms
- private static final int HAL_ERROR_RETRY_MAX = 10;
+ private static final int HAL_ERROR_RETRY_MAX = 20;
private final Runnable mCancelNotReceived = new Runnable() {
@Override
@@ -682,7 +683,12 @@
public void run() {
Log.w(TAG, "Retrying fingerprint after HW unavailable, attempt " +
mHardwareFingerprintUnavailableRetryCount);
- updateFingerprintListeningState();
+ if (mFpm.isHardwareDetected()) {
+ updateFingerprintListeningState();
+ } else if (mHardwareFingerprintUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
+ mHardwareFingerprintUnavailableRetryCount++;
+ mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
+ }
}
};
@@ -704,11 +710,7 @@
}
if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) {
- if (mHardwareFingerprintUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
- mHardwareFingerprintUnavailableRetryCount++;
- mHandler.removeCallbacks(mRetryFingerprintAuthentication);
- mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
- }
+ mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
}
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
@@ -1234,7 +1236,7 @@
private final FingerprintManager.LockoutResetCallback mFingerprintLockoutResetCallback
= new FingerprintManager.LockoutResetCallback() {
@Override
- public void onLockoutReset() {
+ public void onLockoutReset(int sensorId) {
handleFingerprintLockoutReset();
}
};
@@ -1242,7 +1244,7 @@
private final FaceManager.LockoutResetCallback mFaceLockoutResetCallback
= new FaceManager.LockoutResetCallback() {
@Override
- public void onLockoutReset() {
+ public void onLockoutReset(int sensorId) {
handleFaceLockoutReset();
}
};
@@ -1770,6 +1772,8 @@
mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback);
}
+ mIsAutomotive = isAutomotive();
+
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
mUserManager = context.getSystemService(UserManager.class);
mIsPrimaryUser = mUserManager.isPrimaryUser();
@@ -1838,7 +1842,7 @@
if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
return;
}
- mHandler.removeCallbacks(mRetryFingerprintAuthentication);
+
boolean shouldListenForFingerprint = shouldListenForFingerprint();
boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
|| mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
@@ -2484,6 +2488,14 @@
.addCategory(Intent.CATEGORY_HOME);
ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity(homeIntent,
0 /* flags */);
+
+ // TODO(b/160971249): Replace in the future by resolving activity as user.
+ if (resolveInfo == null && mIsAutomotive) {
+ Log.w(TAG, "resolveNeedsSlowUnlockTransition: returning false since activity "
+ + "could not be resolved.");
+ return false;
+ }
+
return FALLBACK_HOME_COMPONENT.equals(resolveInfo.getComponentInfo().getComponentName());
}
@@ -2554,6 +2566,10 @@
return false;
}
+ private boolean isAutomotive() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ }
+
/**
* Remove the given observer's callback.
*
@@ -2990,5 +3006,8 @@
pw.println(" " + time + " " + model.toString());
}
}
+ if (mIsAutomotive) {
+ pw.println(" Running on Automotive build");
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index 332a00d..398a2c9 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -16,6 +16,7 @@
package com.android.systemui.accessibility;
+import android.annotation.NonNull;
import android.content.Context;
import android.graphics.PixelFormat;
import android.provider.Settings;
@@ -23,6 +24,7 @@
import android.view.WindowManager;
import android.widget.ImageView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
/**
@@ -42,23 +44,36 @@
private boolean mIsVisible = false;
MagnificationModeSwitch(Context context) {
+ this(context, createView(context));
+ }
+
+ @VisibleForTesting
+ MagnificationModeSwitch(Context context, @NonNull ImageView imageView) {
mContext = context;
mWindowManager = (WindowManager) mContext.getSystemService(
Context.WINDOW_SERVICE);
mParams = createLayoutParams();
- mImageView = createView(mContext, mMagnificationMode);
+ mImageView = imageView;
+ applyResourcesValues();
mImageView.setOnClickListener(
view -> {
removeButton();
toggleMagnificationMode();
});
+ mImageView.setImageResource(getIconResId(mMagnificationMode));
+ }
+
+ private void applyResourcesValues() {
+ final int padding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnification_switch_button_padding);
+ mImageView.setPadding(padding, padding, padding, padding);
}
void removeButton() {
- mImageView.animate().cancel();
if (!mIsVisible) {
return;
}
+ mImageView.animate().cancel();
mWindowManager.removeView(mImageView);
mIsVisible = false;
}
@@ -72,7 +87,7 @@
mWindowManager.addView(mImageView, mParams);
mIsVisible = true;
}
-
+ mImageView.setAlpha(1.0f);
// TODO(b/143852371): use accessibility timeout as a delay.
// Dismiss the magnification switch button after the button is displayed for a period of
// time.
@@ -82,30 +97,29 @@
.setStartDelay(START_DELAY_MS)
.setDuration(DURATION_MS)
.withEndAction(
- () -> removeButton());
+ () -> removeButton())
+ .start();
}
private void toggleMagnificationMode() {
final int newMode =
mMagnificationMode ^ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
mMagnificationMode = newMode;
+ mImageView.setImageResource(getIconResId(newMode));
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, newMode);
}
- private static ImageView createView(Context context, int mode) {
- final int padding = context.getResources().getDimensionPixelSize(
- R.dimen.magnification_switch_button_padding);
+ private static ImageView createView(Context context) {
ImageView imageView = new ImageView(context);
- imageView.setImageResource(getIconResId(mode));
imageView.setClickable(true);
imageView.setFocusable(true);
- imageView.setPadding(padding, padding, padding, padding);
imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
return imageView;
}
- private static int getIconResId(int mode) {
+ @VisibleForTesting
+ static int getIconResId(int mode) {
return (mode == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
? R.drawable.ic_open_in_new_window
: R.drawable.ic_open_in_new_fullscreen;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 97e97ff..04f2892 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -156,6 +156,7 @@
}
private void hideUdfpsOverlay() {
+ onFingerUp();
mHandler.post(() -> {
Log.v(TAG, "hideUdfpsOverlay | removing window");
if (mIsOverlayShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index dfc7d53..8190550 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -143,12 +143,12 @@
void onFingerDown() {
mIsFingerDown = true;
mSensorPaint.setStyle(Paint.Style.FILL);
- invalidate();
+ postInvalidate();
}
void onFingerUp() {
mIsFingerDown = false;
mSensorPaint.setStyle(Paint.Style.STROKE);
- invalidate();
+ postInvalidate();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 76370a9..b328f62 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -1586,6 +1586,11 @@
Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
}
+ if (bubbleToSelect == null) {
+ mBubbleData.setShowingOverflow(false);
+ return;
+ }
+
// Ignore this new bubble only if it is the exact same bubble object. Otherwise, we'll want
// to re-render it even if it has the same key (equals() returns true). If the currently
// expanded bubble is removed and instantly re-added, we'll get back a new Bubble instance
@@ -1594,10 +1599,11 @@
if (mExpandedBubble == bubbleToSelect) {
return;
}
- if (bubbleToSelect == null || bubbleToSelect.getKey() != BubbleOverflow.KEY) {
- mBubbleData.setShowingOverflow(false);
- } else {
+
+ if (bubbleToSelect.getKey() == BubbleOverflow.KEY) {
mBubbleData.setShowingOverflow(true);
+ } else {
+ mBubbleData.setShowingOverflow(false);
}
if (mIsExpanded && mIsExpansionAnimating) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index 78d7087..e8dba8f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -217,10 +217,10 @@
private fun dumpMessage(message: LogMessage, pw: PrintWriter) {
pw.print(DATE_FORMAT.format(message.timestamp))
pw.print(" ")
- pw.print(message.level)
+ pw.print(message.level.shortString)
pw.print(" ")
pw.print(message.tag)
- pw.print(" ")
+ pw.print(": ")
pw.println(message.printer(message))
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt
index 7b9af0f..53f231c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt
@@ -21,11 +21,14 @@
/**
* Enum version of @Log.Level
*/
-enum class LogLevel(@Log.Level val nativeLevel: Int) {
- VERBOSE(Log.VERBOSE),
- DEBUG(Log.DEBUG),
- INFO(Log.INFO),
- WARNING(Log.WARN),
- ERROR(Log.ERROR),
- WTF(Log.ASSERT)
+enum class LogLevel(
+ @Log.Level val nativeLevel: Int,
+ val shortString: String
+) {
+ VERBOSE(Log.VERBOSE, "V"),
+ DEBUG(Log.DEBUG, "D"),
+ INFO(Log.INFO, "I"),
+ WARNING(Log.WARN, "W"),
+ ERROR(Log.ERROR, "E"),
+ WTF(Log.ASSERT, "WTF")
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 87df544..2802702 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -356,17 +356,8 @@
@Override
public void onPipTransitionStarted(ComponentName activity, int direction) {
if (isOutPipDirection(direction)) {
- // On phones, the expansion animation that happens on pip tap before restoring
- // to fullscreen makes it so that the bounds received here are the expanded
- // bounds. We want to restore to the unexpanded bounds when re-entering pip,
- // so we save the bounds before expansion (normal) instead of the current
- // bounds.
- mReentryBounds.set(mTouchHandler.getNormalBounds());
- // Apply the snap fraction of the current bounds to the normal bounds.
- final Rect bounds = mPipTaskOrganizer.getLastReportedBounds();
- float snapFraction = mPipBoundsHandler.getSnapFraction(bounds);
- mPipBoundsHandler.applySnapFraction(mReentryBounds, snapFraction);
- // Save reentry bounds (normal non-expand bounds with current position applied).
+ // Exiting PIP, save the reentry bounds to restore to when re-entering.
+ updateReentryBounds();
mPipBoundsHandler.onSaveReentryBounds(activity, mReentryBounds);
}
// Disable touches while the animation is running
@@ -380,6 +371,23 @@
}
}
+ /**
+ * Update the bounds used to save the re-entry size and snap fraction when exiting PIP.
+ */
+ public void updateReentryBounds() {
+ // On phones, the expansion animation that happens on pip tap before restoring
+ // to fullscreen makes it so that the last reported bounds are the expanded
+ // bounds. We want to restore to the unexpanded bounds when re-entering pip,
+ // so we use the bounds before expansion (normal) instead of the reported
+ // bounds.
+ Rect reentryBounds = mTouchHandler.getNormalBounds();
+ // Apply the snap fraction of the current bounds to the normal bounds.
+ final Rect bounds = mPipTaskOrganizer.getLastReportedBounds();
+ float snapFraction = mPipBoundsHandler.getSnapFraction(bounds);
+ mPipBoundsHandler.applySnapFraction(reentryBounds, snapFraction);
+ mReentryBounds.set(reentryBounds);
+ }
+
@Override
public void onPipTransitionFinished(ComponentName activity, int direction) {
onPipTransitionFinishedOrCanceled(direction);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 442b43e..89df372 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -104,6 +104,8 @@
private static final int INITIAL_DISMISS_DELAY = 3500;
private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
private static final long MENU_FADE_DURATION = 125;
+ private static final long MENU_SLOW_FADE_DURATION = 175;
+ private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30;
private static final float MENU_BACKGROUND_ALPHA = 0.3f;
private static final float DISMISS_BACKGROUND_ALPHA = 0.6f;
@@ -183,6 +185,7 @@
break;
}
case MESSAGE_MENU_EXPANDED : {
+ mMenuContainerAnimator.setStartDelay(MENU_SHOW_ON_EXPAND_START_DELAY);
mMenuContainerAnimator.start();
break;
}
@@ -401,7 +404,9 @@
mMenuContainerAnimator.playTogether(dismissAnim, resizeAnim);
}
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
- mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
+ mMenuContainerAnimator.setDuration(menuState == MENU_STATE_CLOSE
+ ? MENU_FADE_DURATION
+ : MENU_SLOW_FADE_DURATION);
if (allowMenuTimeout) {
mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 10b04c0..6abbbbe 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -24,6 +24,7 @@
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioAttributes;
@@ -376,13 +377,15 @@
return;
}
mHighTempWarning = true;
+ final String message = mContext.getString(R.string.high_temp_notif_message);
final Notification.Builder nb =
new Notification.Builder(mContext, NotificationChannels.ALERTS)
.setSmallIcon(R.drawable.ic_device_thermostat_24)
.setWhen(0)
.setShowWhen(false)
.setContentTitle(mContext.getString(R.string.high_temp_title))
- .setContentText(mContext.getString(R.string.high_temp_notif_message))
+ .setContentText(message)
+ .setStyle(new Notification.BigTextStyle().bigText(message))
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setContentIntent(pendingBroadcast(ACTION_CLICKED_TEMP_WARNING))
.setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_TEMP_WARNING))
@@ -402,6 +405,23 @@
d.setPositiveButton(com.android.internal.R.string.ok, null);
d.setShowForAllUsers(true);
d.setOnDismissListener(dialog -> mHighTempDialog = null);
+ final String url = mContext.getString(R.string.high_temp_dialog_help_url);
+ if (!url.isEmpty()) {
+ d.setNeutralButton(R.string.high_temp_dialog_help_text,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final Intent helpIntent =
+ new Intent(Intent.ACTION_VIEW)
+ .setData(Uri.parse(url))
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Dependency.get(ActivityStarter.class).startActivity(helpIntent,
+ true /* dismissShade */, resultCode -> {
+ mHighTempDialog = null;
+ });
+ }
+ });
+ }
d.show();
mHighTempDialog = d;
}
@@ -420,19 +440,38 @@
d.setPositiveButton(com.android.internal.R.string.ok, null);
d.setShowForAllUsers(true);
d.setOnDismissListener(dialog -> mThermalShutdownDialog = null);
+ final String url = mContext.getString(R.string.thermal_shutdown_dialog_help_url);
+ if (!url.isEmpty()) {
+ d.setNeutralButton(R.string.thermal_shutdown_dialog_help_text,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final Intent helpIntent =
+ new Intent(Intent.ACTION_VIEW)
+ .setData(Uri.parse(url))
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Dependency.get(ActivityStarter.class).startActivity(helpIntent,
+ true /* dismissShade */, resultCode -> {
+ mThermalShutdownDialog = null;
+ });
+ }
+ });
+ }
d.show();
mThermalShutdownDialog = d;
}
@Override
public void showThermalShutdownWarning() {
+ final String message = mContext.getString(R.string.thermal_shutdown_message);
final Notification.Builder nb =
new Notification.Builder(mContext, NotificationChannels.ALERTS)
.setSmallIcon(R.drawable.ic_device_thermostat_24)
.setWhen(0)
.setShowWhen(false)
.setContentTitle(mContext.getString(R.string.thermal_shutdown_title))
- .setContentText(mContext.getString(R.string.thermal_shutdown_message))
+ .setContentText(message)
+ .setStyle(new Notification.BigTextStyle().bigText(message))
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setContentIntent(pendingBroadcast(ACTION_CLICKED_THERMAL_SHUTDOWN_WARNING))
.setDeleteIntent(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 2ef6934..201ed9c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -100,7 +100,8 @@
if (item.picture == null) {
v.bind(name, getDrawable(mContext, item).mutate(), item.resolveId());
} else {
- int avatarSize = (int) v.getResources().getDimension(R.dimen.qs_framed_avatar_size);
+ int avatarSize =
+ (int) mContext.getResources().getDimension(R.dimen.qs_framed_avatar_size);
Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
drawable.setColorFilter(
item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 8a38199..370f9a7 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -193,10 +193,11 @@
@Override
public void onKeyguardShowingChanged() {
- if (!isDividerVisible() || mView == null) {
+ if (!isSplitActive() || mView == null) {
return;
}
mView.setHidden(mKeyguardStateController.isShowing());
+ mImePositionProcessor.updateAdjustForIme();
}
@Override
@@ -285,8 +286,9 @@
* while this only cares if some things are (eg. while entering/exiting as well).
*/
private boolean isSplitActive() {
- return mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED
- || mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED;
+ return mSplits.mPrimary != null && mSplits.mSecondary != null
+ && (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED
+ || mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED);
}
private void addDivider(Configuration configuration) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
index 47c8c0a..9db389e 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
@@ -91,6 +91,7 @@
private boolean mPaused = true;
private boolean mPausedTargetAdjusted = false;
+ private boolean mAdjustedWhileHidden = false;
DividerImeController(SplitScreenTaskOrganizer splits, TransactionPool pool, Handler handler) {
mSplits = splits;
@@ -170,11 +171,17 @@
// If split is hidden, we don't want to trigger any relayouts that would cause the
// divider to show again.
updateImeAdjustState();
+ } else {
+ mAdjustedWhileHidden = true;
}
}
private void updateImeAdjustState() {
- if (mAdjusted != mTargetAdjusted) {
+ updateImeAdjustState(false /* force */);
+ }
+
+ private void updateImeAdjustState(boolean force) {
+ if (mAdjusted != mTargetAdjusted || force) {
// Reposition the server's secondary split position so that it evaluates
// insets properly.
WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -231,6 +238,11 @@
mSplits.mDivider.setAdjustedForIme(mTargetShown && !mPaused);
}
+ public void updateAdjustForIme() {
+ updateImeAdjustState(mAdjustedWhileHidden);
+ mAdjustedWhileHidden = false;
+ }
+
@Override
public void onImePositionChanged(int displayId, int imeTop,
SurfaceControl.Transaction t) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 27daf86..837543c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -591,6 +591,7 @@
.registerDisplayListener(this, new Handler(Looper.getMainLooper()));
mOrientationHandle = new QuickswitchOrientedNavHandle(getContext());
+ mOrientationHandle.setId(R.id.secondary_home_handle);
getBarTransitions().addDarkIntensityListener(mOrientationHandleIntensityListener);
mOrientationParams = new WindowManager.LayoutParams(0, 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index 5ec5ec6..f52a6e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -288,7 +288,8 @@
if (item.picture == null) {
v.bind(name, getDrawable(mContext, item).mutate(), item.resolveId());
} else {
- int avatarSize = (int) v.getResources().getDimension(R.dimen.kg_framed_avatar_size);
+ int avatarSize =
+ (int) mContext.getResources().getDimension(R.dimen.kg_framed_avatar_size);
Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
drawable.setColorFilter(
item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
index 38b20c0..3402a52 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -278,10 +278,8 @@
if (imeSource == null || mImeSourceControl == null) {
return;
}
- // Set frame, but only if the new frame isn't empty -- this maintains continuity
final Rect newFrame = imeSource.getFrame();
- mImeFrame.set(newFrame);
- final boolean isFloating = newFrame.height() == 0;
+ final boolean isFloating = newFrame.height() == 0 && show;
if (isFloating) {
// This is likely a "floating" or "expanded" IME, so to get animations, just
// pretend the ime has some size just below the screen.
@@ -290,6 +288,9 @@
mSystemWindows.mDisplayController.getDisplayLayout(mDisplayId).density()
* FLOATING_IME_BOTTOM_INSET);
mImeFrame.bottom -= floatingInset;
+ } else if (newFrame.height() != 0) {
+ // Don't set a new frame if it's empty and hiding -- this maintains continuity
+ mImeFrame.set(newFrame);
}
if (DEBUG) {
Slog.d(TAG, "Run startAnim show:" + show + " was:"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
new file mode 100644
index 0000000..71f3d5b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2020 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.accessibility;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+
+import static com.android.systemui.accessibility.MagnificationModeSwitch.getIconResId;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MagnificationModeSwitchTest extends SysuiTestCase {
+
+ @Mock
+ private ImageView mMockImageView;
+ @Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private ViewPropertyAnimator mViewPropertyAnimator;
+ private MagnificationModeSwitch mMagnificationModeSwitch;
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+
+ when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.setStartDelay(anyLong())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.withEndAction(any(Runnable.class))).thenReturn(
+ mViewPropertyAnimator);
+
+ when(mMockImageView.animate()).thenReturn(mViewPropertyAnimator);
+
+ mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mMockImageView);
+ }
+
+ @Test
+ public void removeButton_removeView() {
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+ mMagnificationModeSwitch.removeButton();
+
+ verify(mWindowManager).removeView(mMockImageView);
+ // First invocation is in showButton.
+ verify(mViewPropertyAnimator, times(2)).cancel();
+ }
+
+ @Test
+ public void showWindowModeButton_fullscreenMode_addViewAndSetImageResource() {
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+ verify(mMockImageView).setAlpha(1.0f);
+ verify(mMockImageView).setImageResource(
+ getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
+ verify(mViewPropertyAnimator).cancel();
+ verify(mViewPropertyAnimator).setDuration(anyLong());
+ verify(mViewPropertyAnimator).setStartDelay(anyLong());
+ verify(mViewPropertyAnimator).alpha(anyFloat());
+ ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mViewPropertyAnimator).withEndAction(captor.capture());
+ verify(mWindowManager).addView(eq(mMockImageView), any(WindowManager.LayoutParams.class));
+
+ captor.getValue().run();
+
+ // First invocation is in showButton.
+ verify(mViewPropertyAnimator, times(2)).cancel();
+ verify(mWindowManager).removeView(mMockImageView);
+ }
+
+ @Test
+ public void performClick_fullscreenMode_removeViewAndChangeSettingsValue() {
+ ArgumentCaptor<View.OnClickListener> captor = ArgumentCaptor.forClass(
+ View.OnClickListener.class);
+ verify(mMockImageView).setOnClickListener(captor.capture());
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+
+ captor.getValue().onClick(mMockImageView);
+
+ // First invocation is in showButton.
+ verify(mViewPropertyAnimator, times(2)).cancel();
+ verify(mMockImageView).setImageResource(
+ getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
+ verify(mWindowManager).removeView(mMockImageView);
+ final int actualMode = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0);
+ assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, actualMode);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index 6d6a4d8..f48b3fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -25,6 +25,8 @@
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.util.UserIcons
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.qs.QSUserSwitcherEvent
import com.android.systemui.statusbar.policy.UserSwitcherController
@@ -50,10 +52,10 @@
@Mock private lateinit var mOtherView: View
@Mock private lateinit var mInflatedUserDetailItemView: UserDetailItemView
@Mock private lateinit var mUserInfo: UserInfo
- @Mock private lateinit var mPicture: Bitmap
@Mock private lateinit var mLayoutInflater: LayoutInflater
private lateinit var adapter: UserDetailView.Adapter
private lateinit var uiEventLogger: UiEventLoggerFake
+ private lateinit var mPicture: Bitmap
@Before
fun setUp() {
@@ -64,6 +66,7 @@
`when`(mLayoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
.thenReturn(mInflatedUserDetailItemView)
adapter = UserDetailView.Adapter(mContext, mUserSwitcherController, uiEventLogger)
+ mPicture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
}
private fun clickableTest(
@@ -141,4 +144,4 @@
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */)
-}
\ No newline at end of file
+}
diff --git a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index e10bab4..9bb01ae 100644
--- a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -102,17 +102,21 @@
private UiAutomation mUiAutomation =
InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ private boolean mRunTests;
@Before
public void setUp() throws Exception {
- mHandlerThread = new HandlerThread(getClass().getSimpleName());
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
- mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm);
// Needed to create a TestNetworkInterface, to call requestTetheredInterface, and to receive
// tethered client callbacks.
mUiAutomation.adoptShellPermissionIdentity(
MANAGE_TEST_NETWORKS, NETWORK_SETTINGS, TETHER_PRIVILEGED);
+ mRunTests = mTm.isTetheringSupported() && mEm != null;
+ assumeTrue(mRunTests);
+
+ mHandlerThread = new HandlerThread(getClass().getSimpleName());
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm);
}
private void cleanUp() throws Exception {
@@ -136,7 +140,7 @@
@After
public void tearDown() throws Exception {
try {
- cleanUp();
+ if (mRunTests) cleanUp();
} finally {
mUiAutomation.dropShellPermissionIdentity();
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index a4c5a23..9ad808a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -79,6 +79,7 @@
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
+import com.android.server.accessibility.magnification.FullScreenMagnificationController;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -89,6 +90,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.Set;
/**
@@ -205,7 +207,8 @@
/**
* @return The magnification controller
*/
- @NonNull MagnificationController getMagnificationController();
+ @NonNull
+ FullScreenMagnificationController getFullScreenMagnificationController();
/**
* Called back to notify system that the client has changed
@@ -831,7 +834,7 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- return mSystemSupport.getMagnificationController().getScale(displayId);
+ return mSystemSupport.getFullScreenMagnificationController().getScale(displayId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -844,8 +847,8 @@
if (!hasRightsToCurrentUserLocked()) {
return region;
}
- MagnificationController magnificationController =
- mSystemSupport.getMagnificationController();
+ FullScreenMagnificationController magnificationController =
+ mSystemSupport.getFullScreenMagnificationController();
boolean registeredJustForThisCall =
registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
@@ -867,8 +870,8 @@
if (!hasRightsToCurrentUserLocked()) {
return 0.0f;
}
- MagnificationController magnificationController =
- mSystemSupport.getMagnificationController();
+ FullScreenMagnificationController magnificationController =
+ mSystemSupport.getFullScreenMagnificationController();
boolean registeredJustForThisCall =
registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
@@ -889,8 +892,8 @@
if (!hasRightsToCurrentUserLocked()) {
return 0.0f;
}
- MagnificationController magnificationController =
- mSystemSupport.getMagnificationController();
+ FullScreenMagnificationController magnificationController =
+ mSystemSupport.getFullScreenMagnificationController();
boolean registeredJustForThisCall =
registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
@@ -906,7 +909,7 @@
}
private boolean registerMagnificationIfNeeded(int displayId,
- MagnificationController magnificationController) {
+ FullScreenMagnificationController magnificationController) {
if (!magnificationController.isRegistered(displayId)
&& mSecurityPolicy.canControlMagnification(this)) {
magnificationController.register(displayId);
@@ -927,8 +930,8 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- MagnificationController magnificationController =
- mSystemSupport.getMagnificationController();
+ FullScreenMagnificationController magnificationController =
+ mSystemSupport.getFullScreenMagnificationController();
return (magnificationController.reset(displayId, animate)
|| !magnificationController.isMagnifying(displayId));
} finally {
@@ -948,8 +951,8 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- MagnificationController magnificationController =
- mSystemSupport.getMagnificationController();
+ FullScreenMagnificationController magnificationController =
+ mSystemSupport.getFullScreenMagnificationController();
if (!magnificationController.isRegistered(displayId)) {
magnificationController.register(displayId);
}
@@ -1176,7 +1179,11 @@
/* ignore */
}
if (mService != null) {
- mService.unlinkToDeath(this, 0);
+ try {
+ mService.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Slog.e(LOG_TAG, "Failed unregistering death link");
+ }
mService = null;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index be470b1..8b40f61 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -34,6 +34,7 @@
import com.android.server.LocalServices;
import com.android.server.accessibility.gestures.TouchExplorer;
+import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler;
import com.android.server.policy.WindowManagerPolicy;
@@ -539,7 +540,7 @@
detectControlGestures, triggerable, displayId);
} else {
magnificationGestureHandler = new FullScreenMagnificationGestureHandler(displayContext,
- mAms.getMagnificationController(), mAms::onMagnificationScaleChanged,
+ mAms.getFullScreenMagnificationController(), mAms::onMagnificationScaleChanged,
detectControlGestures, triggerable, displayId);
}
return magnificationGestureHandler;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 9f9ceb0..c92571c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -116,6 +116,7 @@
import com.android.internal.util.IntPair;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.accessibility.magnification.FullScreenMagnificationController;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
import com.android.server.accessibility.magnification.WindowMagnificationManager;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -212,7 +213,7 @@
// Lazily initialized - access through getSystemActionPerfomer()
private SystemActionPerformer mSystemActionPerformer;
- private MagnificationController mMagnificationController;
+ private FullScreenMagnificationController mFullScreenMagnificationController;
private InteractionBridge mInteractionBridge;
@@ -2191,13 +2192,13 @@
return;
}
- if (mMagnificationController != null) {
- mMagnificationController.setUserId(userState.mUserId);
+ if (mFullScreenMagnificationController != null) {
+ mFullScreenMagnificationController.setUserId(userState.mUserId);
}
if (mUiAutomationManager.suppressingAccessibilityServicesLocked()
- && mMagnificationController != null) {
- mMagnificationController.unregisterAll();
+ && mFullScreenMagnificationController != null) {
+ mFullScreenMagnificationController.unregisterAll();
return;
}
@@ -2209,7 +2210,7 @@
|| userState.isShortcutMagnificationEnabledLocked()) {
for (int i = 0; i < displays.size(); i++) {
final Display display = displays.get(i);
- getMagnificationController().register(display.getDisplayId());
+ getFullScreenMagnificationController().register(display.getDisplayId());
}
return;
}
@@ -2219,9 +2220,9 @@
final Display display = displays.get(i);
final int displayId = display.getDisplayId();
if (userHasListeningMagnificationServicesLocked(userState, displayId)) {
- getMagnificationController().register(displayId);
- } else if (mMagnificationController != null) {
- mMagnificationController.unregister(displayId);
+ getFullScreenMagnificationController().register(displayId);
+ } else if (mFullScreenMagnificationController != null) {
+ mFullScreenMagnificationController.unregister(displayId);
}
}
}
@@ -2568,7 +2569,7 @@
}
// In case user assigned magnification to the given shortcut.
if (targetName.equals(MAGNIFICATION_CONTROLLER_NAME)) {
- final boolean enabled = !getMagnificationController().isMagnifying(displayId);
+ final boolean enabled = !getFullScreenMagnificationController().isMagnifying(displayId);
logAccessibilityShortcutActivated(MAGNIFICATION_COMPONENT_NAME, shortcutType, enabled);
sendAccessibilityButtonToInputFilter(displayId);
return;
@@ -2928,13 +2929,14 @@
}
@Override
- public MagnificationController getMagnificationController() {
+ public FullScreenMagnificationController getFullScreenMagnificationController() {
synchronized (mLock) {
- if (mMagnificationController == null) {
- mMagnificationController = new MagnificationController(mContext, this, mLock);
- mMagnificationController.setUserId(mCurrentUserId);
+ if (mFullScreenMagnificationController == null) {
+ mFullScreenMagnificationController = new FullScreenMagnificationController(mContext,
+ this, mLock);
+ mFullScreenMagnificationController.setUserId(mCurrentUserId);
}
- return mMagnificationController;
+ return mFullScreenMagnificationController;
}
}
@@ -3126,8 +3128,8 @@
}
}
}
- if (mMagnificationController != null) {
- mMagnificationController.onDisplayRemoved(displayId);
+ if (mFullScreenMagnificationController != null) {
+ mFullScreenMagnificationController.onDisplayRemoved(displayId);
}
mA11yWindowManager.stopTrackingWindows(displayId);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index fea2e7b..e48d11d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -120,7 +120,7 @@
AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
userState.removeServiceLocked(this);
- mSystemSupport.getMagnificationController().resetAllIfNeeded(mId);
+ mSystemSupport.getFullScreenMagnificationController().resetAllIfNeeded(mId);
mActivityTaskManagerService.setAllowAppSwitches(mComponentName.flattenToString(), -1,
userState.mUserId);
resetLocked();
@@ -312,7 +312,7 @@
userState.serviceDisconnectedLocked(this);
}
resetLocked();
- mSystemSupport.getMagnificationController().resetAllIfNeeded(mId);
+ mSystemSupport.getFullScreenMagnificationController().resetAllIfNeeded(mId);
mSystemSupport.onClientChangeLocked(false);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
similarity index 94%
rename from services/accessibility/java/com/android/server/accessibility/MagnificationController.java
rename to services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index e784056..44c4bf4 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.accessibility;
+package com.android.server.accessibility.magnification;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
@@ -42,6 +42,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
+import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.wm.WindowManagerInternal;
import java.util.Locale;
@@ -56,9 +57,9 @@
* magnification region. If a value is out of bounds, it will be adjusted to guarantee these
* constraints.
*/
-public class MagnificationController {
+public class FullScreenMagnificationController {
private static final boolean DEBUG = false;
- private static final String LOG_TAG = "MagnificationController";
+ private static final String LOG_TAG = "FullScreenMagnificationController";
public static final float MIN_SCALE = 1.0f;
public static final float MAX_SCALE = 8.0f;
@@ -140,11 +141,12 @@
/**
* Unregisters magnification callback from window manager. Callbacks to
- * {@link MagnificationController#unregisterCallbackLocked(int, boolean)} after
+ * {@link FullScreenMagnificationController#unregisterCallbackLocked(int, boolean)} after
* unregistered.
*
* @param delete true if this instance should be removed from the SparseArray in
- * MagnificationController after unregistered, for example, display removed.
+ * FullScreenMagnificationController after unregistered, for example,
+ * display removed.
*/
@GuardedBy("mLock")
void unregister(boolean delete) {
@@ -164,7 +166,8 @@
* called after animation finished.
*
* @param delete true if this instance should be removed from the SparseArray in
- * MagnificationController after unregistered, for example, display removed.
+ * FullScreenMagnificationController after unregistered, for example,
+ * display removed.
*/
@GuardedBy("mLock")
void unregisterPending(boolean delete) {
@@ -257,15 +260,17 @@
@Override
public void onRotationChanged(int rotation) {
// Treat as context change and reset
- final Message m = PooledLambda.obtainMessage(MagnificationController::resetIfNeeded,
- MagnificationController.this, mDisplayId, true);
+ final Message m = PooledLambda.obtainMessage(
+ FullScreenMagnificationController::resetIfNeeded,
+ FullScreenMagnificationController.this, mDisplayId, true);
mControllerCtx.getHandler().sendMessage(m);
}
@Override
public void onUserContextChanged() {
- final Message m = PooledLambda.obtainMessage(MagnificationController::resetIfNeeded,
- MagnificationController.this, mDisplayId, true);
+ final Message m = PooledLambda.obtainMessage(
+ FullScreenMagnificationController::resetIfNeeded,
+ FullScreenMagnificationController.this, mDisplayId, true);
mControllerCtx.getHandler().sendMessage(m);
}
@@ -554,25 +559,25 @@
float getMinOffsetXLocked() {
final float viewportWidth = mMagnificationBounds.width();
final float viewportLeft = mMagnificationBounds.left;
- return (viewportLeft + viewportWidth) -
- (viewportLeft + viewportWidth) * mCurrentMagnificationSpec.scale;
+ return (viewportLeft + viewportWidth)
+ - (viewportLeft + viewportWidth) * mCurrentMagnificationSpec.scale;
}
float getMaxOffsetXLocked() {
- return mMagnificationBounds.left -
- mMagnificationBounds.left * mCurrentMagnificationSpec.scale;
+ return mMagnificationBounds.left
+ - mMagnificationBounds.left * mCurrentMagnificationSpec.scale;
}
float getMinOffsetYLocked() {
final float viewportHeight = mMagnificationBounds.height();
final float viewportTop = mMagnificationBounds.top;
- return (viewportTop + viewportHeight) -
- (viewportTop + viewportHeight) * mCurrentMagnificationSpec.scale;
+ return (viewportTop + viewportHeight)
+ - (viewportTop + viewportHeight) * mCurrentMagnificationSpec.scale;
}
float getMaxOffsetYLocked() {
- return mMagnificationBounds.top -
- mMagnificationBounds.top * mCurrentMagnificationSpec.scale;
+ return mMagnificationBounds.top
+ - mMagnificationBounds.top * mCurrentMagnificationSpec.scale;
}
@Override
@@ -590,9 +595,9 @@
}
/**
- * MagnificationController Constructor
+ * FullScreenMagnificationController Constructor
*/
- public MagnificationController(@NonNull Context context,
+ public FullScreenMagnificationController(@NonNull Context context,
@NonNull AccessibilityManagerService ams, @NonNull Object lock) {
this(new ControllerContext(context, ams,
LocalServices.getService(WindowManagerInternal.class),
@@ -604,7 +609,7 @@
* Constructor for tests
*/
@VisibleForTesting
- public MagnificationController(@NonNull ControllerContext ctx, @NonNull Object lock) {
+ public FullScreenMagnificationController(@NonNull ControllerContext ctx, @NonNull Object lock) {
mControllerCtx = ctx;
mLock = lock;
mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
@@ -1088,7 +1093,7 @@
private void onScreenTurnedOff() {
final Message m = PooledLambda.obtainMessage(
- MagnificationController::resetAllIfNeeded, this, false);
+ FullScreenMagnificationController::resetAllIfNeeded, this, false);
mControllerCtx.getHandler().sendMessage(m);
}
@@ -1253,14 +1258,14 @@
synchronized (mLock) {
if (mEnabled) {
float fract = animation.getAnimatedFraction();
- mTmpMagnificationSpec.scale = mStartMagnificationSpec.scale +
- (mEndMagnificationSpec.scale - mStartMagnificationSpec.scale) * fract;
- mTmpMagnificationSpec.offsetX = mStartMagnificationSpec.offsetX +
- (mEndMagnificationSpec.offsetX - mStartMagnificationSpec.offsetX)
- * fract;
- mTmpMagnificationSpec.offsetY = mStartMagnificationSpec.offsetY +
- (mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY)
- * fract;
+ mTmpMagnificationSpec.scale = mStartMagnificationSpec.scale
+ + (mEndMagnificationSpec.scale - mStartMagnificationSpec.scale) * fract;
+ mTmpMagnificationSpec.offsetX = mStartMagnificationSpec.offsetX
+ + (mEndMagnificationSpec.offsetX - mStartMagnificationSpec.offsetX)
+ * fract;
+ mTmpMagnificationSpec.offsetY = mStartMagnificationSpec.offsetY
+ + (mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY)
+ * fract;
setMagnificationSpecLocked(mTmpMagnificationSpec);
}
}
@@ -1269,10 +1274,10 @@
private static class ScreenStateObserver extends BroadcastReceiver {
private final Context mContext;
- private final MagnificationController mController;
+ private final FullScreenMagnificationController mController;
private boolean mRegistered = false;
- public ScreenStateObserver(Context context, MagnificationController controller) {
+ ScreenStateObserver(Context context, FullScreenMagnificationController controller) {
mContext = context;
mController = controller;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
similarity index 94%
rename from services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java
rename to services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 058a293..d50e9d7 100644
--- a/services/accessibility/java/com/android/server/accessibility/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.accessibility;
+package com.android.server.accessibility.magnification;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
@@ -59,8 +59,8 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.gestures.GestureUtils;
-import com.android.server.accessibility.magnification.MagnificationGestureHandler;
import java.util.ArrayDeque;
import java.util.Queue;
@@ -114,7 +114,7 @@
* 7. The magnification scale will be persisted in settings and in the cloud.
*/
@SuppressWarnings("WeakerAccess")
-class FullScreenMagnificationGestureHandler extends MagnificationGestureHandler {
+public class FullScreenMagnificationGestureHandler extends MagnificationGestureHandler {
private static final String LOG_TAG = "FullScreenMagnificationGestureHandler";
private static final boolean DEBUG_ALL = false;
@@ -127,9 +127,9 @@
// to AccessibilityService.MagnificationController#setScale() has
// different scale range
private static final float MIN_SCALE = 2.0f;
- private static final float MAX_SCALE = MagnificationController.MAX_SCALE;
+ private static final float MAX_SCALE = FullScreenMagnificationController.MAX_SCALE;
- @VisibleForTesting final MagnificationController mMagnificationController;
+ @VisibleForTesting final FullScreenMagnificationController mFullScreenMagnificationController;
@VisibleForTesting final DelegatingState mDelegatingState;
@VisibleForTesting final DetectingState mDetectingState;
@@ -163,7 +163,7 @@
/**
* @param context Context for resolving various magnification-related resources
- * @param magnificationController the {@link MagnificationController}
+ * @param fullScreenMagnificationController the {@link FullScreenMagnificationController}
*
* @param detectTripleTap {@code true} if this detector should detect and respond to triple-tap
* gestures for engaging and disengaging magnification,
@@ -173,9 +173,9 @@
* {@code false} if it should ignore such triggers.
* @param displayId The logical display id.
*/
- FullScreenMagnificationGestureHandler(Context context,
- MagnificationController magnificationController,
- MagnificationGestureHandler.ScaleChangedListener listener,
+ public FullScreenMagnificationGestureHandler(Context context,
+ FullScreenMagnificationController fullScreenMagnificationController,
+ ScaleChangedListener listener,
boolean detectTripleTap,
boolean detectShortcutTrigger,
int displayId) {
@@ -185,7 +185,7 @@
"FullScreenMagnificationGestureHandler(detectTripleTap = " + detectTripleTap
+ ", detectShortcutTrigger = " + detectShortcutTrigger + ")");
}
- mMagnificationController = magnificationController;
+ mFullScreenMagnificationController = fullScreenMagnificationController;
mDisplayId = displayId;
mDelegatingState = new DelegatingState();
@@ -265,7 +265,7 @@
mScreenStateReceiver.unregister();
}
// Check if need to reset when MagnificationGestureHandler is the last magnifying service.
- mMagnificationController.resetAllIfNeeded(
+ mFullScreenMagnificationController.resetAllIfNeeded(
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
clearAndTransitionToStateDetecting();
}
@@ -273,7 +273,7 @@
@Override
public void notifyShortcutTriggered() {
if (mDetectShortcutTrigger) {
- boolean wasMagnifying = mMagnificationController.resetIfNeeded(mDisplayId,
+ boolean wasMagnifying = mFullScreenMagnificationController.resetIfNeeded(mDisplayId,
/* animate */ true);
if (wasMagnifying) {
clearAndTransitionToStateDetecting();
@@ -424,7 +424,7 @@
}
public void persistScaleAndTransitionTo(State state) {
- mMagnificationController.persistScale();
+ mFullScreenMagnificationController.persistScale();
clear();
transitionTo(state);
}
@@ -439,7 +439,7 @@
Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
+ " scrollY: " + distanceY);
}
- mMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
+ mFullScreenMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
return /* event consumed: */ true;
}
@@ -455,7 +455,7 @@
mScaling = abs(deltaScale) > mScalingThreshold;
return mScaling;
}
- final float initialScale = mMagnificationController.getScale(mDisplayId);
+ final float initialScale = mFullScreenMagnificationController.getScale(mDisplayId);
final float targetScale = initialScale * detector.getScaleFactor();
// Don't allow a gesture to move the user further outside the
@@ -477,7 +477,7 @@
final float pivotX = detector.getFocusX();
final float pivotY = detector.getFocusY();
if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x");
- mMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
+ mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
mListener.onMagnificationScaleChanged(mDisplayId, getMode());
return /* handled: */ true;
@@ -537,9 +537,9 @@
}
final float eventX = event.getX();
final float eventY = event.getY();
- if (mMagnificationController.magnificationRegionContains(
+ if (mFullScreenMagnificationController.magnificationRegionContains(
mDisplayId, eventX, eventY)) {
- mMagnificationController.setCenter(mDisplayId, eventX, eventY,
+ mFullScreenMagnificationController.setCenter(mDisplayId, eventX, eventY,
/* animate */ mLastMoveOutsideMagnifiedRegion,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
mLastMoveOutsideMagnifiedRegion = false;
@@ -687,7 +687,7 @@
mLastDetectingDownEventTime = event.getDownTime();
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
- if (!mMagnificationController.magnificationRegionContains(
+ if (!mFullScreenMagnificationController.magnificationRegionContains(
mDisplayId, event.getX(), event.getY())) {
transitionToDelegatingStateAndClear();
@@ -705,7 +705,7 @@
// If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
// to ensure reachability of
// STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
- || mMagnificationController.isMagnifying(mDisplayId)) {
+ || mFullScreenMagnificationController.isMagnifying(mDisplayId)) {
afterMultiTapTimeoutTransitionToDelegatingState();
@@ -717,7 +717,7 @@
}
break;
case ACTION_POINTER_DOWN: {
- if (mMagnificationController.isMagnifying(mDisplayId)
+ if (mFullScreenMagnificationController.isMagnifying(mDisplayId)
&& event.getPointerCount() == 2) {
storeSecondPointerDownLocation(event);
mHandler.sendEmptyMessageDelayed(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE,
@@ -760,7 +760,7 @@
mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
- if (!mMagnificationController.magnificationRegionContains(
+ if (!mFullScreenMagnificationController.magnificationRegionContains(
mDisplayId, event.getX(), event.getY())) {
transitionToDelegatingStateAndClear();
@@ -811,7 +811,7 @@
// Only log the triple tap event, use numTaps to filter.
if (multitapTriggered && numTaps > 2) {
- final boolean enabled = mMagnificationController.isMagnifying(mDisplayId);
+ final boolean enabled = mFullScreenMagnificationController.isMagnifying(mDisplayId);
logMagnificationTripleTap(enabled);
}
return multitapTriggered;
@@ -947,7 +947,7 @@
clear();
// Toggle zoom
- if (mMagnificationController.isMagnifying(mDisplayId)) {
+ if (mFullScreenMagnificationController.isMagnifying(mDisplayId)) {
zoomOff();
} else {
zoomOn(up.getX(), up.getY());
@@ -955,7 +955,7 @@
}
private boolean isMagnifying() {
- return mMagnificationController.isMagnifying(mDisplayId);
+ return mFullScreenMagnificationController.isMagnifying(mDisplayId);
}
void transitionToViewportDraggingStateAndClear(MotionEvent down) {
@@ -964,7 +964,7 @@
clear();
mViewportDraggingState.mZoomedInBeforeDrag =
- mMagnificationController.isMagnifying(mDisplayId);
+ mFullScreenMagnificationController.isMagnifying(mDisplayId);
// Triple tap and hold also belongs to triple tap event.
final boolean enabled = !mViewportDraggingState.mZoomedInBeforeDrag;
@@ -995,7 +995,7 @@
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "setShortcutTriggered(" + state + ")");
mShortcutTriggered = state;
- mMagnificationController.setForceShowMagnifiableBounds(mDisplayId, state);
+ mFullScreenMagnificationController.setForceShowMagnifiableBounds(mDisplayId, state);
}
/**
@@ -1028,9 +1028,9 @@
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOn(" + centerX + ", " + centerY + ")");
final float scale = MathUtils.constrain(
- mMagnificationController.getPersistedScale(),
+ mFullScreenMagnificationController.getPersistedScale(),
MIN_SCALE, MAX_SCALE);
- mMagnificationController.setScaleAndCenter(mDisplayId,
+ mFullScreenMagnificationController.setScaleAndCenter(mDisplayId,
scale, centerX, centerY,
/* animate */ true,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -1038,7 +1038,7 @@
private void zoomOff() {
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()");
- mMagnificationController.reset(mDisplayId, /* animate */ true);
+ mFullScreenMagnificationController.reset(mDisplayId, /* animate */ true);
}
private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) {
@@ -1059,7 +1059,7 @@
+ ", mDetectShortcutTrigger=" + mDetectShortcutTrigger
+ ", mCurrentState=" + State.nameOf(mCurrentState)
+ ", mPreviousState=" + State.nameOf(mPreviousState)
- + ", mMagnificationController=" + mMagnificationController
+ + ", mMagnificationController=" + mFullScreenMagnificationController
+ ", mDisplayId=" + mDisplayId
+ '}';
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
index f38c38f..d6f53d2 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -23,7 +23,7 @@
*/
public abstract class MagnificationGestureHandler extends BaseEventStreamTransformation {
- protected final MagnificationGestureHandler.ScaleChangedListener mListener;
+ protected final ScaleChangedListener mListener;
protected MagnificationGestureHandler(ScaleChangedListener listener) {
mListener = listener;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 56c0519..bd25f2b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -100,7 +100,7 @@
*/
public WindowMagnificationGestureHandler(Context context,
WindowMagnificationManager windowMagnificationMgr,
- MagnificationGestureHandler.ScaleChangedListener listener, boolean detectTripleTap,
+ ScaleChangedListener listener, boolean detectTripleTap,
boolean detectShortcutTrigger, int displayId) {
super(listener);
if (DEBUG_ALL) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index b5ea01a..a08c2dd 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -17,7 +17,10 @@
package com.android.server.accessibility.magnification;
import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
@@ -34,7 +37,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
-import com.android.server.accessibility.MagnificationController;
import com.android.server.statusbar.StatusBarManagerInternal;
/**
@@ -50,20 +52,31 @@
private static final String TAG = "WindowMagnificationMgr";
//Ensure the range has consistency with full screen.
- static final float MAX_SCALE = MagnificationController.MAX_SCALE;
- static final float MIN_SCALE = MagnificationController.MIN_SCALE;
+ static final float MAX_SCALE = FullScreenMagnificationController.MAX_SCALE;
+ static final float MIN_SCALE = FullScreenMagnificationController.MIN_SCALE;
- private final Object mLock = new Object();;
+ private final Object mLock = new Object();
private final Context mContext;
@VisibleForTesting
@GuardedBy("mLock")
- @Nullable WindowMagnificationConnectionWrapper mConnectionWrapper;
+ @Nullable
+ WindowMagnificationConnectionWrapper mConnectionWrapper;
@GuardedBy("mLock")
private ConnectionCallback mConnectionCallback;
@GuardedBy("mLock")
private SparseArray<WindowMagnifier> mWindowMagnifiers = new SparseArray<>();
private int mUserId;
+ @VisibleForTesting
+ protected final BroadcastReceiver mScreenStateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int displayId = context.getDisplayId();
+ removeMagnificationButton(displayId);
+ disableWindowMagnification(displayId);
+ }
+ };
+
public WindowMagnificationManager(Context context, int userId) {
mContext = context;
mUserId = userId;
@@ -134,8 +147,12 @@
if (connect == isConnected()) {
return false;
}
- if (!connect) {
+ if (connect) {
+ final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(mScreenStateReceiver, intentFilter);
+ } else {
disableAllWindowMagnifiers();
+ mContext.unregisterReceiver(mScreenStateReceiver);
}
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index b13bef2..c839ce94 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -24,6 +24,7 @@
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.backup.BackupManager;
+import android.app.backup.BackupManager.OperationType;
import android.app.backup.IBackupManager;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
@@ -1338,14 +1339,15 @@
if (!isUserReadyForBackup(userId)) {
return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
}
- return requestBackup(userId, packages, observer, monitor, flags);
+ return requestBackup(userId, packages, observer, monitor, flags, OperationType.BACKUP);
}
@Override
public int requestBackup(String[] packages, IBackupObserver observer,
- IBackupManagerMonitor monitor, int flags) throws RemoteException {
- return requestBackupForUser(binderGetCallingUserId(), packages,
- observer, monitor, flags);
+ IBackupManagerMonitor monitor, int flags, @OperationType int operationType)
+ throws RemoteException {
+ return requestBackup(binderGetCallingUserId(), packages,
+ observer, monitor, flags, operationType);
}
/**
@@ -1357,13 +1359,15 @@
String[] packages,
IBackupObserver observer,
IBackupManagerMonitor monitor,
- int flags) {
+ int flags,
+ @OperationType int operationType) {
UserBackupManagerService userBackupManagerService =
getServiceForUserIfCallerHasPermission(userId, "requestBackup()");
return userBackupManagerService == null
? BackupManager.ERROR_BACKUP_NOT_ALLOWED
- : userBackupManagerService.requestBackup(packages, observer, monitor, flags);
+ : userBackupManagerService.requestBackup(packages, observer, monitor, flags,
+ operationType);
}
@Override
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index b7f775b..d6a075f 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -47,6 +47,7 @@
import android.app.PendingIntent;
import android.app.backup.BackupAgent;
import android.app.backup.BackupManager;
+import android.app.backup.BackupManager.OperationType;
import android.app.backup.BackupManagerMonitor;
import android.app.backup.FullBackup;
import android.app.backup.IBackupManager;
@@ -535,11 +536,12 @@
}
@VisibleForTesting
- UserBackupManagerService(Context context) {
+ UserBackupManagerService(Context context, PackageManager packageManager) {
mContext = context;
mUserId = 0;
mRegisterTransportsRequestedTime = 0;
+ mPackageManager = packageManager;
mBaseStateDir = null;
mDataDir = null;
@@ -550,7 +552,6 @@
mRunInitIntent = null;
mAgentTimeoutParameters = null;
mTransportManager = null;
- mPackageManager = null;
mActivityManagerInternal = null;
mAlarmManager = null;
mConstants = null;
@@ -1825,6 +1826,15 @@
*/
public int requestBackup(String[] packages, IBackupObserver observer,
IBackupManagerMonitor monitor, int flags) {
+ return requestBackup(packages, observer, monitor, flags, OperationType.BACKUP);
+ }
+
+ /**
+ * Requests a backup for the inputted {@code packages} with a specified {@link
+ * IBackupManagerMonitor} and {@link OperationType}.
+ */
+ public int requestBackup(String[] packages, IBackupObserver observer,
+ IBackupManagerMonitor monitor, int flags, @OperationType int operationType) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup");
if (packages == null || packages.length < 1) {
@@ -1872,6 +1882,18 @@
OnTaskFinishedListener listener =
caller -> mTransportManager.disposeOfTransportClient(transportClient, caller);
+ Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
+ msg.obj = getRequestBackupParams(packages, observer, monitor, flags, operationType,
+ transportClient, transportDirName, listener);
+ mBackupHandler.sendMessage(msg);
+ return BackupManager.SUCCESS;
+ }
+
+ @VisibleForTesting
+ BackupParams getRequestBackupParams(String[] packages, IBackupObserver observer,
+ IBackupManagerMonitor monitor, int flags, @OperationType int operationType,
+ TransportClient transportClient, String transportDirName,
+ OnTaskFinishedListener listener) {
ArrayList<String> fullBackupList = new ArrayList<>();
ArrayList<String> kvBackupList = new ArrayList<>();
for (String packageName : packages) {
@@ -1882,12 +1904,13 @@
try {
PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageName,
PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
- if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo, mUserId)) {
+ if (!appIsEligibleForBackup(packageInfo.applicationInfo, mUserId,
+ operationType)) {
BackupObserverUtils.sendBackupOnPackageResult(observer, packageName,
BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
}
- if (AppBackupUtils.appGetsFullBackup(packageInfo)) {
+ if (appGetsFullBackup(packageInfo, operationType)) {
fullBackupList.add(packageInfo.packageName);
} else {
kvBackupList.add(packageInfo.packageName);
@@ -1897,6 +1920,7 @@
BackupManager.ERROR_PACKAGE_NOT_FOUND);
}
}
+
EventLog.writeEvent(EventLogTags.BACKUP_REQUESTED, packages.length, kvBackupList.size(),
fullBackupList.size());
if (MORE_DEBUG) {
@@ -1915,11 +1939,20 @@
boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0;
- Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
- msg.obj = new BackupParams(transportClient, transportDirName, kvBackupList, fullBackupList,
- observer, monitor, listener, true, nonIncrementalBackup);
- mBackupHandler.sendMessage(msg);
- return BackupManager.SUCCESS;
+ return new BackupParams(transportClient, transportDirName, kvBackupList, fullBackupList,
+ observer, monitor, listener, /* userInitiated */ true, nonIncrementalBackup,
+ operationType);
+ }
+
+ @VisibleForTesting
+ boolean appIsEligibleForBackup(ApplicationInfo applicationInfo, int userId,
+ @OperationType int operationType) {
+ return AppBackupUtils.appIsEligibleForBackup(applicationInfo, userId, operationType);
+ }
+
+ @VisibleForTesting
+ boolean appGetsFullBackup(PackageInfo packageInfo, @OperationType int operationType) {
+ return AppBackupUtils.appGetsFullBackup(packageInfo, operationType);
}
/** Cancel all running backups. */
diff --git a/services/backup/java/com/android/server/backup/params/BackupParams.java b/services/backup/java/com/android/server/backup/params/BackupParams.java
index 2ba8ec1..514434e 100644
--- a/services/backup/java/com/android/server/backup/params/BackupParams.java
+++ b/services/backup/java/com/android/server/backup/params/BackupParams.java
@@ -16,6 +16,7 @@
package com.android.server.backup.params;
+import android.app.backup.BackupManager.OperationType;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
@@ -35,11 +36,12 @@
public OnTaskFinishedListener listener;
public boolean userInitiated;
public boolean nonIncrementalBackup;
+ @OperationType public int operationType;
public BackupParams(TransportClient transportClient, String dirName,
ArrayList<String> kvPackages, ArrayList<String> fullPackages, IBackupObserver observer,
IBackupManagerMonitor monitor, OnTaskFinishedListener listener, boolean userInitiated,
- boolean nonIncrementalBackup) {
+ boolean nonIncrementalBackup, int operationType) {
this.transportClient = transportClient;
this.dirName = dirName;
this.kvPackages = kvPackages;
@@ -49,5 +51,6 @@
this.listener = listener;
this.userInitiated = userInitiated;
this.nonIncrementalBackup = nonIncrementalBackup;
+ this.operationType = operationType;
}
}
diff --git a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
index 35dfccf..a616e0e 100644
--- a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
@@ -23,6 +23,7 @@
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.annotation.Nullable;
+import android.app.backup.BackupManager.OperationType;
import android.app.backup.BackupTransport;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -66,14 +67,23 @@
*/
public static boolean appIsEligibleForBackup(ApplicationInfo app, int userId) {
return appIsEligibleForBackup(
- app, LocalServices.getService(PackageManagerInternal.class), userId);
+ app, LocalServices.getService(PackageManagerInternal.class), userId,
+ OperationType.BACKUP);
+ }
+
+ public static boolean appIsEligibleForBackup(ApplicationInfo app, int userId,
+ @OperationType int operationType) {
+ return appIsEligibleForBackup(
+ app, LocalServices.getService(PackageManagerInternal.class), userId, operationType);
}
@VisibleForTesting
static boolean appIsEligibleForBackup(
- ApplicationInfo app, PackageManagerInternal packageManager, int userId) {
+ ApplicationInfo app, PackageManagerInternal packageManager, int userId,
+ @OperationType int operationType) {
// 1. their manifest states android:allowBackup="false"
- if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) {
+ boolean appAllowsBackup = (app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
+ if (!appAllowsBackup && !forceFullBackup(app.uid, operationType)) {
return false;
}
@@ -189,6 +199,16 @@
* policy!
*/
public static boolean appGetsFullBackup(PackageInfo pkg) {
+ return appGetsFullBackup(pkg, OperationType.BACKUP);
+ }
+
+ @VisibleForTesting
+ public static boolean appGetsFullBackup(PackageInfo pkg, @OperationType int operationType) {
+ if (forceFullBackup(pkg.applicationInfo.uid, operationType)) {
+ // If this is a migration, all non-system packages get full backup.
+ return true;
+ }
+
if (pkg.applicationInfo.backupAgentName != null) {
// If it has an agent, it gets full backups only if it says so
return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FULL_BACKUP_ONLY) != 0;
@@ -198,6 +218,16 @@
return true;
}
+ public static boolean appIgnoresIncludeExcludeRules(ApplicationInfo app,
+ @OperationType int operationType) {
+ return forceFullBackup(app.uid, operationType);
+ }
+
+ private static boolean forceFullBackup(int appUid, @OperationType int operationType) {
+ return operationType == OperationType.MIGRATION &&
+ !UserHandle.isCore(appUid);
+ }
+
/**
* Returns whether the app is only capable of doing key/value. We say it's not if it allows full
* backup, and it is otherwise.
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
index a91f2f6..e5d0021 100644
--- a/services/core/java/com/android/server/ServiceWatcher.java
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -56,8 +56,7 @@
/**
* Maintains a binding to the best service that matches the given intent information. Bind and
- * unbind callbacks, as well as all binder operations, will all be run on a single thread, but the
- * exact thread is left undefined.
+ * unbind callbacks, as well as all binder operations, will all be run on a single thread.
*/
public class ServiceWatcher implements ServiceConnection {
@@ -73,7 +72,11 @@
public interface BinderRunner {
/** Called to run client code with the binder. */
void run(IBinder binder) throws RemoteException;
- /** Called if an error occurred and the function could not be run. */
+ /**
+ * Called if an error occurred and the function could not be run. This callback is only
+ * intended for resource deallocation and cleanup in response to a single binder operation,
+ * it should not be used to propagate errors further.
+ */
default void onError() {}
}
@@ -189,8 +192,15 @@
public ServiceWatcher(Context context, String action,
@Nullable BinderRunner onBind, @Nullable Runnable onUnbind,
@BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
+ this(context, FgThread.getHandler(), action, onBind, onUnbind, enableOverlayResId,
+ nonOverlayPackageResId);
+ }
+
+ public ServiceWatcher(Context context, Handler handler, String action,
+ @Nullable BinderRunner onBind, @Nullable Runnable onUnbind,
+ @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
mContext = context;
- mHandler = FgThread.getHandler();
+ mHandler = handler;
mIntent = new Intent(Objects.requireNonNull(action));
Resources resources = context.getResources();
@@ -278,13 +288,6 @@
return true;
}
- /**
- * Returns information on the currently selected service.
- */
- public ServiceInfo getBoundService() {
- return mServiceInfo;
- }
-
private void onBestServiceChanged(boolean forceRebind) {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
@@ -380,7 +383,7 @@
}
@Override
- public void onBindingDied(ComponentName component) {
+ public final void onBindingDied(ComponentName component) {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
if (D) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 84bd59b..816d663 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -4532,9 +4532,10 @@
ServiceManager.getServiceOrThrow("vold"));
for (String pkg : packageList) {
final String packageObbDir =
- String.format("/storage/emulated/%d/Android/obb/%s/", userId, pkg);
+ String.format(Locale.US, "/storage/emulated/%d/Android/obb/%s/",
+ userId, pkg);
final String packageDataDir =
- String.format("/storage/emulated/%d/Android/data/%s/",
+ String.format(Locale.US, "/storage/emulated/%d/Android/data/%s/",
userId, pkg);
// Create package obb and data dir if it doesn't exist.
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 3d1691d..676b767 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1707,7 +1707,7 @@
apn = preciseState.getDataConnectionApn();
state = preciseState.getState();
networkType = preciseState.getNetworkType();
- linkProps = preciseState.getDataConnectionLinkProperties();
+ linkProps = preciseState.getLinkProperties();
}
if (VDBG) {
log("notifyDataConnectionForSubscriber: subId=" + subId
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 5124c4a..a2eea13 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -470,22 +470,23 @@
// if this receiver was slow, impose deferral policy on the app. This will kick in
// when processNextBroadcastLocked() next finds this uid as a receiver identity.
if (!r.timeoutExempt) {
- if (mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) {
+ // r.curApp can be null if finish has raced with process death - benign
+ // edge case, and we just ignore it because we're already cleaning up
+ // as expected.
+ if (r.curApp != null
+ && mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) {
// Core system packages are exempt from deferral policy
if (!UserHandle.isCore(r.curApp.uid)) {
if (DEBUG_BROADCAST_DEFERRAL) {
Slog.i(TAG_BROADCAST, "Broadcast receiver " + (r.nextReceiver - 1)
+ " was slow: " + receiver + " br=" + r);
}
- if (r.curApp != null) {
- mDispatcher.startDeferring(r.curApp.uid);
- } else {
- Slog.d(TAG_BROADCAST, "finish receiver curApp is null? " + r);
- }
+ mDispatcher.startDeferring(r.curApp.uid);
} else {
if (DEBUG_BROADCAST_DEFERRAL) {
Slog.i(TAG_BROADCAST, "Core uid " + r.curApp.uid
- + " receiver was slow but not deferring: " + receiver + " br=" + r);
+ + " receiver was slow but not deferring: "
+ + receiver + " br=" + r);
}
}
}
diff --git a/services/core/java/com/android/server/audio/SoundEffectsHelper.java b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
index 84d2ff8..27d5767 100644
--- a/services/core/java/com/android/server/audio/SoundEffectsHelper.java
+++ b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
@@ -441,11 +441,12 @@
onUnloadSoundEffects();
break;
case MSG_PLAY_EFFECT:
+ final int effect = msg.arg1, volume = msg.arg2;
onLoadSoundEffects(new OnEffectsLoadCompleteHandler() {
@Override
public void run(boolean success) {
if (success) {
- onPlaySoundEffect(msg.arg1 /*effect*/, msg.arg2 /*volume*/);
+ onPlaySoundEffect(effect, volume);
}
}
});
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 6be45747..03ca8fa 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static com.android.server.biometrics.PreAuthInfo.AUTHENTICATOR_OK;
@@ -30,19 +31,34 @@
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE;
import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
+import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.PromptInfo;
+import android.os.Binder;
import android.os.Build;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.Slog;
+import com.android.internal.R;
+import com.android.server.biometrics.sensors.ClientMonitor;
+
public class Utils {
+
+ private static final String TAG = "BiometricUtils";
+
public static boolean isDebugEnabled(Context context, int targetUserId) {
if (targetUserId == UserHandle.USER_NULL) {
return false;
@@ -331,4 +347,58 @@
}
return false;
}
+
+ public static void checkPermission(Context context, String permission) {
+ context.enforceCallingOrSelfPermission(permission,
+ "Must have " + permission + " permission.");
+ }
+
+ public static boolean isCurrentUserOrProfile(Context context, int userId) {
+ UserManager um = UserManager.get(context);
+ if (um == null) {
+ Slog.e(TAG, "Unable to get UserManager");
+ return false;
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Allow current user or profiles of the current user...
+ for (int profileId : um.getEnabledProfileIds(ActivityManager.getCurrentUser())) {
+ if (profileId == userId) {
+ return true;
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return false;
+ }
+
+ public static boolean isStrongBiometric(int sensorId) {
+ IBiometricService service = IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE));
+ try {
+ return Utils.isAtLeastStrength(service.getCurrentStrength(sensorId),
+ Authenticators.BIOMETRIC_STRONG);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException", e);
+ return false;
+ }
+ }
+
+ public static boolean isKeyguard(Context context, String clientPackage) {
+ final boolean hasPermission = context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
+ == PackageManager.PERMISSION_GRANTED;
+
+ final ComponentName keyguardComponent = ComponentName.unflattenFromString(
+ context.getResources().getString(R.string.config_keyguardComponent));
+ final String keyguardPackage = keyguardComponent != null
+ ? keyguardComponent.getPackageName() : null;
+ return hasPermission && keyguardPackage != null && keyguardPackage.equals(clientPackage);
+ }
+
+ public static String getClientName(@Nullable ClientMonitor<?> client) {
+ return client != null ? client.getClass().getSimpleName() : "null";
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index 68cc884..f8e8dd9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -32,8 +32,7 @@
* Abstract {@link ClientMonitor} subclass that operations eligible/interested in acquisition
* messages should extend.
*/
-public abstract class AcquisitionClient<T> extends ClientMonitor<T>
- implements ErrorConsumer, Cancellable {
+public abstract class AcquisitionClient<T> extends ClientMonitor<T> implements Interruptable {
private static final String TAG = "Biometrics/AcquisitionClient";
@@ -46,6 +45,7 @@
private final PowerManager mPowerManager;
private final VibrationEffect mSuccessVibrationEffect;
private final VibrationEffect mErrorVibrationEffect;
+ private boolean mShouldSendErrorToClient;
/**
* Stops the HAL operation specific to the ClientMonitor subclass.
@@ -75,15 +75,43 @@
@Override
public void onError(int errorCode, int vendorCode) {
- logOnError(getContext(), errorCode, vendorCode, getTargetUserId());
+ // Errors from the HAL always finish the client
+ onErrorInternal(errorCode, vendorCode, true /* finish */);
+ }
+
+ protected void onErrorInternal(int errorCode, int vendorCode, boolean finish) {
+ // In some cases, the framework will send an error to the caller before a true terminal
+ // case (success, failure, or error) is received from the HAL (e.g. versions of fingerprint
+ // that do not handle lockout under the HAL. In these cases, ensure that the framework only
+ // sends errors once per ClientMonitor.
+ if (!mShouldSendErrorToClient) {
+ logOnError(getContext(), errorCode, vendorCode, getTargetUserId());
+ try {
+ if (getListener() != null) {
+ mShouldSendErrorToClient = true;
+ getListener().onError(getSensorId(), getCookie(), errorCode, vendorCode);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to invoke sendError", e);
+ }
+ }
+
+ if (finish) {
+ mFinishCallback.onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public void cancelWithoutStarting(@NonNull FinishCallback finishCallback) {
+ final int errorCode = BiometricConstants.BIOMETRIC_ERROR_CANCELED;
try {
if (getListener() != null) {
- getListener().onError(getSensorId(), getCookie(), errorCode, vendorCode);
+ getListener().onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */);
}
} catch (RemoteException e) {
Slog.w(TAG, "Failed to invoke sendError", e);
}
- mFinishCallback.onClientFinished(this, false /* success */);
+ finishCallback.onClientFinished(this, true /* success */);
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index df836d5..fdc3def 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -17,6 +17,7 @@
package com.android.server.biometrics.sensors;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.app.TaskStackListener;
@@ -41,7 +42,7 @@
private final boolean mIsStrongBiometric;
private final boolean mRequireConfirmation;
private final IActivityTaskManager mActivityTaskManager;
- private final TaskStackListener mTaskStackListener;
+ @Nullable private final TaskStackListener mTaskStackListener;
private final LockoutTracker mLockoutTracker;
private final boolean mIsRestricted;
@@ -56,7 +57,7 @@
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int targetUserId, long operationId, boolean restricted, @NonNull String owner,
int cookie, boolean requireConfirmation, int sensorId, boolean isStrongBiometric,
- int statsModality, int statsClient, @NonNull TaskStackListener taskStackListener,
+ int statsModality, int statsClient, @Nullable TaskStackListener taskStackListener,
@NonNull LockoutTracker lockoutTracker) {
super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId,
statsModality, BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
@@ -119,6 +120,13 @@
+ ", requireConfirmation: " + mRequireConfirmation
+ ", user: " + getTargetUserId());
+ final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId());
+ if (isCryptoOperation()) {
+ pm.incrementCryptoAuthForUser(getTargetUserId(), authenticated);
+ } else {
+ pm.incrementAuthForUser(getTargetUserId(), authenticated);
+ }
+
if (authenticated) {
mAlreadyDone = true;
@@ -126,10 +134,12 @@
vibrateSuccess();
}
- try {
- mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
- } catch (RemoteException e) {
- Slog.e(TAG, "Could not unregister task stack listener", e);
+ if (mTaskStackListener != null) {
+ try {
+ mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not unregister task stack listener", e);
+ }
}
final byte[] byteToken = new byte[hardwareAuthToken.size()];
@@ -184,6 +194,18 @@
}
}
+ @Override
+ public void onAcquired(int acquiredInfo, int vendorCode) {
+ super.onAcquired(acquiredInfo, vendorCode);
+
+ final @LockoutTracker.LockoutMode int lockoutMode =
+ mLockoutTracker.getLockoutModeForUser(getTargetUserId());
+ if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
+ PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
+ pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
+ }
+ }
+
/**
* Start authentication
*/
@@ -202,10 +224,12 @@
return;
}
- try {
- mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
- } catch (RemoteException e) {
- Slog.e(TAG, "Could not register task stack listener", e);
+ if (mTaskStackListener != null) {
+ try {
+ mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not register task stack listener", e);
+ }
}
if (DEBUG) Slog.w(TAG, "Requesting auth for " + getOwnerString());
@@ -222,10 +246,12 @@
return;
}
- try {
- mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
- } catch (RemoteException e) {
- Slog.e(TAG, "Could not unregister task stack listener", e);
+ if (mTaskStackListener != null) {
+ try {
+ mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not unregister task stack listener", e);
+ }
}
if (DEBUG) Slog.w(TAG, "Requesting cancel for " + getOwnerString());
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
new file mode 100644
index 0000000..6bdd783
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2020 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.biometrics.sensors;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.IBiometricService;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.text.SimpleDateFormat;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Queue;
+
+/**
+ * A scheduler for biometric HAL operations. Maintains a queue of {@link ClientMonitor} operations,
+ * without caring about its implementation details. Operations may perform one or more
+ * interactions with the HAL before finishing.
+ */
+public class BiometricScheduler {
+
+ private static final String BASE_TAG = "BiometricScheduler";
+
+ /**
+ * Contains all the necessary information for a HAL operation.
+ */
+ private static final class Operation {
+
+ /**
+ * The operation is added to the list of pending operations and waiting for its turn.
+ */
+ static final int STATE_WAITING_IN_QUEUE = 0;
+
+ /**
+ * The operation is added to the list of pending operations, but a subsequent operation
+ * has been added. This state only applies to {@link Interruptable} operations. When this
+ * operation reaches the head of the queue, it will send ERROR_CANCELED and finish.
+ */
+ static final int STATE_WAITING_IN_QUEUE_CANCELING = 1;
+
+ /**
+ * The operation has reached the front of the queue and has started.
+ */
+ static final int STATE_STARTED = 2;
+
+ /**
+ * The operation was started, but is now canceling. Operations should wait for the HAL to
+ * acknowledge that the operation was canceled, at which point it finishes.
+ */
+ static final int STATE_STARTED_CANCELING = 3;
+
+ /**
+ * The operation has reached the head of the queue but is waiting for BiometricService
+ * to acknowledge and start the operation.
+ */
+ static final int STATE_WAITING_FOR_COOKIE = 4;
+
+ /**
+ * The {@link ClientMonitor.FinishCallback} has been invoked and the client is finished.
+ */
+ static final int STATE_FINISHED = 5;
+
+ @IntDef({STATE_WAITING_IN_QUEUE,
+ STATE_WAITING_IN_QUEUE_CANCELING,
+ STATE_STARTED,
+ STATE_STARTED_CANCELING,
+ STATE_WAITING_FOR_COOKIE,
+ STATE_FINISHED})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface OperationState {}
+
+ @NonNull final ClientMonitor<?> clientMonitor;
+ @Nullable final ClientMonitor.FinishCallback clientFinishCallback;
+ @OperationState int state;
+
+ Operation(@NonNull ClientMonitor<?> clientMonitor,
+ @Nullable ClientMonitor.FinishCallback finishCallback) {
+ this.clientMonitor = clientMonitor;
+ this.clientFinishCallback = finishCallback;
+ state = STATE_WAITING_IN_QUEUE;
+ }
+
+ @Override
+ public String toString() {
+ return clientMonitor + ", State: " + state;
+ }
+ }
+
+ /**
+ * Monitors an operation's cancellation. If cancellation takes too long, the watchdog will
+ * kill the current operation and forcibly start the next.
+ */
+ private static final class CancellationWatchdog implements Runnable {
+ static final int DELAY_MS = 3000;
+
+ final String tag;
+ final Operation operation;
+ CancellationWatchdog(String tag, Operation operation) {
+ this.tag = tag;
+ this.operation = operation;
+ }
+
+ @Override
+ public void run() {
+ if (operation.state != Operation.STATE_FINISHED) {
+ Slog.e(tag, "[Watchdog Triggered]: " + operation);
+ operation.clientMonitor.mFinishCallback
+ .onClientFinished(operation.clientMonitor, false /* success */);
+ }
+ }
+ }
+
+ private static final class CrashState {
+ static final int NUM_ENTRIES = 10;
+ final String timestamp;
+ final String currentOperation;
+ final List<String> pendingOperations;
+
+ CrashState(String timestamp, String currentOperation, List<String> pendingOperations) {
+ this.timestamp = timestamp;
+ this.currentOperation = currentOperation;
+ this.pendingOperations = pendingOperations;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(timestamp).append(": ");
+ sb.append("Current Operation: {").append(currentOperation).append("}");
+ sb.append(", Pending Operations(").append(pendingOperations.size()).append(")");
+
+ if (!pendingOperations.isEmpty()) {
+ sb.append(": ");
+ }
+ for (int i = 0; i < pendingOperations.size(); i++) {
+ sb.append(pendingOperations.get(i));
+ if (i < pendingOperations.size() - 1) {
+ sb.append(", ");
+ }
+ }
+ return sb.toString();
+ }
+ }
+
+ @NonNull private final String mBiometricTag;
+ @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
+ @NonNull private final IBiometricService mBiometricService;
+ @NonNull private final Handler mHandler = new Handler(Looper.getMainLooper());
+ @NonNull private final InternalFinishCallback mInternalFinishCallback;
+ @NonNull private final Queue<Operation> mPendingOperations;
+ @Nullable private Operation mCurrentOperation;
+ @NonNull private final ArrayDeque<CrashState> mCrashStates;
+
+ // Internal finish callback, notified when an operation is complete. Notifies the requester
+ // that the operation is complete, before performing internal scheduler work (such as
+ // starting the next client).
+ private class InternalFinishCallback implements ClientMonitor.FinishCallback {
+ @Override
+ public void onClientFinished(ClientMonitor<?> clientMonitor, boolean success) {
+ mHandler.post(() -> {
+ if (mCurrentOperation == null) {
+ Slog.e(getTag(), "[Finishing] " + clientMonitor
+ + " but current operation is null, success: " + success
+ + ", possible lifecycle bug in clientMonitor implementation?");
+ return;
+ }
+
+ mCurrentOperation.state = Operation.STATE_FINISHED;
+
+ if (mCurrentOperation.clientFinishCallback != null) {
+ mCurrentOperation.clientFinishCallback.onClientFinished(clientMonitor, success);
+ }
+
+ if (clientMonitor != mCurrentOperation.clientMonitor) {
+ throw new IllegalStateException("Mismatched operation, "
+ + " current: " + mCurrentOperation.clientMonitor
+ + " received: " + clientMonitor);
+ }
+
+ Slog.d(getTag(), "[Finished] " + clientMonitor + ", success: " + success);
+ if (mGestureAvailabilityDispatcher != null) {
+ mGestureAvailabilityDispatcher.markSensorActive(
+ mCurrentOperation.clientMonitor.getSensorId(), false /* active */);
+ }
+
+ mCurrentOperation = null;
+ startNextOperationIfIdle();
+ });
+ }
+ }
+
+ /**
+ * Creates a new scheduler.
+ * @param tag for the specific instance of the scheduler. Should be unique.
+ * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures
+ * (such as fingerprint swipe).
+ */
+ public BiometricScheduler(@NonNull String tag,
+ @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ mBiometricTag = tag;
+ mInternalFinishCallback = new InternalFinishCallback();
+ mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
+ mPendingOperations = new ArrayDeque<>();
+ mBiometricService = IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE));
+ mCrashStates = new ArrayDeque<>();
+ }
+
+ private String getTag() {
+ return BASE_TAG + "/" + mBiometricTag;
+ }
+
+ private void startNextOperationIfIdle() {
+ if (mCurrentOperation != null) {
+ Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
+ return;
+ }
+ if (mPendingOperations.isEmpty()) {
+ Slog.d(getTag(), "No operations, returning to idle");
+ return;
+ }
+
+ mCurrentOperation = mPendingOperations.poll();
+ final ClientMonitor<?> currentClient = mCurrentOperation.clientMonitor;
+
+ // If the operation at the front of the queue has been marked for cancellation, send
+ // ERROR_CANCELED. No need to start this client.
+ if (mCurrentOperation.state == Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
+ Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation);
+ if (!(currentClient instanceof Interruptable)) {
+ throw new IllegalStateException("Mis-implemented client or scheduler, "
+ + "trying to cancel non-interruptable operation: " + mCurrentOperation);
+ }
+
+ final Interruptable interruptable = (Interruptable) currentClient;
+ interruptable.cancelWithoutStarting(mInternalFinishCallback);
+ // Now we wait for the client to send its FinishCallback, which kicks off the next
+ // operation.
+ return;
+ }
+
+ if (mGestureAvailabilityDispatcher != null
+ && mCurrentOperation.clientMonitor instanceof AcquisitionClient) {
+ mGestureAvailabilityDispatcher.markSensorActive(
+ mCurrentOperation.clientMonitor.getSensorId(),
+ true /* active */);
+ }
+
+ // Not all operations start immediately. BiometricPrompt waits for its operation
+ // to arrive at the head of the queue, before pinging it to start.
+ final boolean shouldStartNow = currentClient.getCookie() == 0;
+ if (shouldStartNow) {
+ Slog.d(getTag(), "[Starting] " + mCurrentOperation);
+ currentClient.start(mInternalFinishCallback);
+ mCurrentOperation.state = Operation.STATE_STARTED;
+ } else {
+ try {
+ mBiometricService.onReadyForAuthentication(currentClient.getCookie());
+ } catch (RemoteException e) {
+ Slog.e(getTag(), "Remote exception when contacting BiometricService", e);
+ }
+ Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation);
+ mCurrentOperation.state = Operation.STATE_WAITING_FOR_COOKIE;
+ }
+ }
+
+ /**
+ * Starts the {@link #mCurrentOperation} if
+ * 1) its state is {@link Operation#STATE_WAITING_FOR_COOKIE} and
+ * 2) its cookie matches this cookie
+ *
+ * This is currently only used by {@link com.android.server.biometrics.BiometricService}, which
+ * requests sensors to prepare for authentication with a cookie. Once sensor(s) are ready (e.g.
+ * the BiometricService client becomes the current client in the scheduler), the cookie is
+ * returned to BiometricService. Once BiometricService decides that authentication can start,
+ * it invokes this code path.
+ *
+ * @param cookie of the operation to be started
+ */
+ public void startPreparedClient(int cookie) {
+ if (mCurrentOperation == null) {
+ Slog.e(getTag(), "Current operation is null");
+ return;
+ }
+ if (mCurrentOperation.state != Operation.STATE_WAITING_FOR_COOKIE) {
+ Slog.e(getTag(), "Operation is in the wrong state: " + mCurrentOperation
+ + ", expected STATE_WAITING_FOR_COOKIE");
+ return;
+ }
+ if (mCurrentOperation.clientMonitor.getCookie() != cookie) {
+ Slog.e(getTag(), "Mismatched cookie for operation: " + mCurrentOperation
+ + ", received: " + cookie);
+ return;
+ }
+
+ Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
+ mCurrentOperation.state = Operation.STATE_STARTED;
+ mCurrentOperation.clientMonitor.start(mInternalFinishCallback);
+ }
+
+ /**
+ * Adds a {@link ClientMonitor} to the pending queue
+ *
+ * @param clientMonitor operation to be scheduled
+ */
+ public void scheduleClientMonitor(@NonNull ClientMonitor<?> clientMonitor) {
+ scheduleClientMonitor(clientMonitor, null /* clientFinishCallback */);
+ }
+
+ /**
+ * Adds a {@link ClientMonitor} to the pending queue
+ *
+ * @param clientMonitor operation to be scheduled
+ * @param clientFinishCallback optional callback, invoked when the client is finished, but
+ * before it has been removed from the queue.
+ */
+ public void scheduleClientMonitor(@NonNull ClientMonitor<?> clientMonitor,
+ @Nullable ClientMonitor.FinishCallback clientFinishCallback) {
+ // Mark any interruptable pending clients as canceling. Once they reach the head of the
+ // queue, the scheduler will send ERROR_CANCELED and skip the operation.
+ for (Operation operation : mPendingOperations) {
+ if (operation.clientMonitor instanceof Interruptable
+ && operation.state != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
+ Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
+ + operation.clientMonitor);
+ operation.state = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
+ }
+ }
+
+ mPendingOperations.add(new Operation(clientMonitor, clientFinishCallback));
+ Slog.d(getTag(), "[Added] " + clientMonitor
+ + ", new queue size: " + mPendingOperations.size());
+
+ // If the current operation is cancellable, start the cancellation process.
+ if (mCurrentOperation != null && mCurrentOperation.clientMonitor instanceof Interruptable
+ && mCurrentOperation.state == Operation.STATE_STARTED) {
+ Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
+ cancelInternal(mCurrentOperation);
+ }
+
+ startNextOperationIfIdle();
+ }
+
+ private void cancelInternal(Operation operation) {
+ if (operation != mCurrentOperation) {
+ Slog.e(getTag(), "cancelInternal invoked on non-current operation: " + operation);
+ return;
+ }
+ if (!(operation.clientMonitor instanceof Interruptable)) {
+ Slog.w(getTag(), "Operation not interruptable: " + operation);
+ return;
+ }
+ if (operation.state == Operation.STATE_STARTED_CANCELING) {
+ Slog.w(getTag(), "Cancel already invoked for operation: " + operation);
+ return;
+ }
+ Slog.d(getTag(), "[Cancelling] Current client: " + operation.clientMonitor);
+ final Interruptable interruptable = (Interruptable) operation.clientMonitor;
+ interruptable.cancel();
+ operation.state = Operation.STATE_STARTED_CANCELING;
+
+ // Add a watchdog. If the HAL does not acknowledge within the timeout, we will
+ // forcibly finish this client.
+ mHandler.postDelayed(new CancellationWatchdog(getTag(), operation),
+ CancellationWatchdog.DELAY_MS);
+ }
+
+ /**
+ * Requests to cancel enrollment.
+ * @param token from the caller, should match the token passed in when requesting enrollment
+ */
+ public void cancelEnrollment(IBinder token) {
+ if (mCurrentOperation == null) {
+ Slog.e(getTag(), "Unable to cancel enrollment, null operation");
+ return;
+ }
+ final boolean isEnrolling = mCurrentOperation.clientMonitor instanceof EnrollClient;
+ final boolean tokenMatches = mCurrentOperation.clientMonitor.getToken() == token;
+ if (!isEnrolling || !tokenMatches) {
+ Slog.w(getTag(), "Not cancelling enrollment, isEnrolling: " + isEnrolling
+ + " tokenMatches: " + tokenMatches);
+ return;
+ }
+
+ cancelInternal(mCurrentOperation);
+ }
+
+ /**
+ * Requests to cancel authentication.
+ * @param token from the caller, should match the token passed in when requesting authentication
+ */
+ public void cancelAuthentication(IBinder token) {
+ if (mCurrentOperation == null) {
+ Slog.e(getTag(), "Unable to cancel authentication, null operation");
+ return;
+ }
+ final boolean isAuthenticating =
+ mCurrentOperation.clientMonitor instanceof AuthenticationClient;
+ final boolean tokenMatches = mCurrentOperation.clientMonitor.getToken() == token;
+ if (!isAuthenticating || !tokenMatches) {
+ Slog.w(getTag(), "Not cancelling authentication, isEnrolling: " + isAuthenticating
+ + " tokenMatches: " + tokenMatches);
+ return;
+ }
+
+ cancelInternal(mCurrentOperation);
+ }
+
+ /**
+ * @return the current operation
+ */
+ public ClientMonitor<?> getCurrentClient() {
+ if (mCurrentOperation == null) {
+ return null;
+ }
+ return mCurrentOperation.clientMonitor;
+ }
+
+ public void recordCrashState() {
+ if (mCrashStates.size() >= CrashState.NUM_ENTRIES) {
+ mCrashStates.removeFirst();
+ }
+ final SimpleDateFormat dateFormat =
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
+ final String timestamp = dateFormat.format(new Date(System.currentTimeMillis()));
+ final List<String> pendingOperations = new ArrayList<>();
+ for (Operation operation : mPendingOperations) {
+ pendingOperations.add(operation.toString());
+ }
+
+ final CrashState crashState = new CrashState(timestamp,
+ mCurrentOperation != null ? mCurrentOperation.toString() : null,
+ pendingOperations);
+ mCrashStates.add(crashState);
+ Slog.e(getTag(), "Recorded crash state: " + crashState.toString());
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println("Dump of BiometricScheduler " + getTag());
+ for (CrashState crashState : mCrashStates) {
+ pw.println("Crash State " + crashState);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceBase.java
deleted file mode 100644
index 9f5abd8..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceBase.java
+++ /dev/null
@@ -1,854 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors;
-
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
-import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
-
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.AppOpsManager;
-import android.app.IActivityTaskManager;
-import android.app.SynchronousUserSwitchObserver;
-import android.app.TaskStackListener;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricManager.Authenticators;
-import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.IBiometricService;
-import android.hardware.fingerprint.Fingerprint;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IHwBinder;
-import android.os.Looper;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Slog;
-
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.SystemService;
-import com.android.server.biometrics.Utils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Abstract base class containing all of the business logic for biometric services, e.g.
- * Fingerprint, Face, Iris.
- *
- * @hide
- */
-public abstract class BiometricServiceBase<T> extends SystemService
- implements IHwBinder.DeathRecipient {
-
- protected static final boolean DEBUG = true;
-
- private static final int MSG_USER_SWITCHING = 10;
- private static final long CANCEL_TIMEOUT_LIMIT = 3000; // max wait for onCancel() from HAL,in ms
-
- private final Context mContext;
- private final String mKeyguardPackage;
- protected final IActivityTaskManager mActivityTaskManager;
- protected final BiometricTaskStackListener mTaskStackListener =
- new BiometricTaskStackListener();
- private final ResetClientStateRunnable mResetClientState = new ResetClientStateRunnable();
-
- protected final IStatusBarService mStatusBarService;
- protected final Map<Integer, Long> mAuthenticatorIds =
- Collections.synchronizedMap(new HashMap<>());
- protected final AppOpsManager mAppOps;
-
- /**
- * Handler which all subclasses should post events to.
- */
- protected final Handler mHandler = new Handler(Looper.getMainLooper()) {
- @Override
- public void handleMessage(android.os.Message msg) {
- switch (msg.what) {
- case MSG_USER_SWITCHING:
- handleUserSwitching(msg.arg1);
- break;
- default:
- Slog.w(getTag(), "Unknown message:" + msg.what);
- }
- }
- };
-
- protected final ClientMonitor.FinishCallback mClientFinishCallback =
- (clientMonitor, success) -> {
- removeClient(clientMonitor);
- // When enrollment finishes, update this group's authenticator id, as the HAL has
- // already generated a new authenticator id when the new biometric is enrolled.
- if (clientMonitor instanceof EnrollClient) {
- updateActiveGroup(clientMonitor.getTargetUserId());
- }
- };
-
- private IBiometricService mBiometricService;
- private ClientMonitor<T> mCurrentClient;
- private ClientMonitor<T> mPendingClient;
- private PerformanceTracker mPerformanceTracker;
- private int mSensorId;
- protected int mCurrentUserId = UserHandle.USER_NULL;
-
- /**
- * @return the log tag.
- */
- protected abstract String getTag();
-
- /**
- * @return a fresh reference to the biometric HAL
- */
- protected abstract T getDaemon();
-
- /**
- * @return the biometric utilities for a specific implementation.
- */
- protected abstract BiometricUtils getBiometricUtils();
-
- /**
- * @param userId
- * @return true if the enrollment limit has been reached.
- */
- protected abstract boolean hasReachedEnrollmentLimit(int userId);
-
- /**
- * Notifies the HAL that the user has changed.
- * @param userId
- */
- protected abstract void updateActiveGroup(int userId);
-
- /**
- * @param userId
- * @return Returns true if the user has any enrolled biometrics.
- */
- protected abstract boolean hasEnrolledBiometrics(int userId);
-
- /**
- * @return Returns the MANAGE_* permission string, which is required for enrollment, removal
- * etc.
- */
- protected abstract String getManageBiometricPermission();
-
- /**
- * Checks if the caller has permission to use the biometric service - throws a SecurityException
- * if not.
- */
- protected abstract void checkUseBiometricPermission();
-
- /**
- * Checks if the caller passes the app ops check
- */
- protected abstract boolean checkAppOps(int uid, String opPackageName);
-
- protected abstract List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates(
- int userId);
-
- /**
- * Notifies clients of any change in the biometric state (active / idle). This is mainly for
- * Fingerprint navigation gestures.
- * @param isActive
- */
- protected void notifyClientActiveCallbacks(boolean isActive) {}
-
- protected abstract int statsModality();
-
- /**
- * @return one of the AuthenticationClient LOCKOUT constants
- */
- protected abstract @LockoutTracker.LockoutMode int getLockoutMode(int userId);
-
- private final Runnable mOnTaskStackChangedRunnable = new Runnable() {
- @Override
- public void run() {
- try {
- if (!(mCurrentClient instanceof AuthenticationClient)) {
- return;
- }
- final String currentClient = mCurrentClient.getOwnerString();
- if (isKeyguard(currentClient)) {
- return; // Keyguard is always allowed
- }
- List<ActivityManager.RunningTaskInfo> runningTasks =
- mActivityTaskManager.getTasks(1);
- if (!runningTasks.isEmpty()) {
- final String topPackage = runningTasks.get(0).topActivity.getPackageName();
- if (!topPackage.contentEquals(currentClient)
- && !mCurrentClient.isAlreadyDone()) {
- Slog.e(getTag(), "Stopping background authentication, top: "
- + topPackage + " currentClient: " + currentClient);
- ((AuthenticationClient) mCurrentClient).cancel();
- }
- }
- } catch (RemoteException e) {
- Slog.e(getTag(), "Unable to get running tasks", e);
- }
- }
- };
-
- private final class BiometricTaskStackListener extends TaskStackListener {
- @Override
- public void onTaskStackChanged() {
- mHandler.post(mOnTaskStackChangedRunnable);
- }
- }
-
- private final class ResetClientStateRunnable implements Runnable {
- @Override
- public void run() {
- /**
- * Warning: if we get here, the driver never confirmed our call to cancel the current
- * operation (authenticate, enroll, remove, enumerate, etc), which is
- * really bad. The result will be a 3-second delay in starting each new client.
- * If you see this on a device, make certain the driver notifies with
- * {@link BiometricConstants#BIOMETRIC_ERROR_CANCELED} in response to cancel()
- * once it has successfully switched to the IDLE state in the HAL.
- * Additionally,{@link BiometricConstants#BIOMETRIC_ERROR_CANCELED} should only be sent
- * in response to an actual cancel() call.
- */
- Slog.w(getTag(), "Client "
- + (mCurrentClient != null ? mCurrentClient.getOwnerString() : "null")
- + " failed to respond to cancel, starting client "
- + (mPendingClient != null ? mPendingClient.getOwnerString() : "null"));
-
- FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
- statsModality(), BiometricsProtoEnums.ISSUE_CANCEL_TIMED_OUT);
-
- ClientMonitor<T> newClient = mPendingClient;
- mCurrentClient = null;
- mPendingClient = null;
- startClient(newClient, false);
- }
- }
-
- /**
- * Initializes the system service.
- * <p>
- * Subclasses must define a single argument constructor that accepts the context
- * and passes it to super.
- * </p>
- *
- * @param context The system server context.
- */
- public BiometricServiceBase(Context context) {
- super(context);
- mContext = context;
- mStatusBarService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
- final ComponentName keyguardComponent = ComponentName.unflattenFromString(
- context.getResources().getString(R.string.config_keyguardComponent));
- mKeyguardPackage = keyguardComponent != null ? keyguardComponent.getPackageName() : null;
- mAppOps = context.getSystemService(AppOpsManager.class);
- mActivityTaskManager = ActivityTaskManager.getService();
- mPerformanceTracker = PerformanceTracker.getInstanceForSensorId(getSensorId());
- }
-
- @Override
- public void onStart() {
- listenForUserSwitches();
- }
-
- @Override
- public void serviceDied(long cookie) {
- Slog.e(getTag(), "HAL died");
- mPerformanceTracker.incrementHALDeathCount();
- mCurrentUserId = UserHandle.USER_NULL;
-
- // All client lifecycle must be managed on the handler.
- mHandler.post(() -> {
- Slog.e(getTag(), "Sending BIOMETRIC_ERROR_HW_UNAVAILABLE after HAL crash");
- handleError(BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
- });
-
- FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
- statsModality(), BiometricsProtoEnums.ISSUE_HAL_DEATH);
- }
-
- protected void initializeConfigurationInternal(int sensorId) {
- if (DEBUG) {
- Slog.d(getTag(), "initializeConfigurationInternal(" + sensorId + ")");
- }
- mSensorId = sensorId;
- }
-
- protected ClientMonitor getCurrentClient() {
- return mCurrentClient;
- }
-
- protected ClientMonitor getPendingClient() {
- return mPendingClient;
- }
-
- protected boolean isStrongBiometric() {
- IBiometricService service = IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE));
- try {
- return Utils.isAtLeastStrength(service.getCurrentStrength(mSensorId),
- Authenticators.BIOMETRIC_STRONG);
- } catch (RemoteException e) {
- Slog.e(getTag(), "RemoteException", e);
- return false;
- }
- }
-
- protected int getSensorId() {
- return mSensorId;
- }
-
- /**
- * Callback handlers from the daemon. The caller must put this on a handler.
- */
-
- protected void handleAcquired(int acquiredInfo, int vendorCode) {
- final ClientMonitor client = mCurrentClient;
- if (!(client instanceof AcquisitionClient)) {
- final String clientName = client != null ? client.getClass().getSimpleName() : "null";
- Slog.e(getTag(), "handleAcquired for non-acquire consumer: " + clientName);
- return;
- }
-
- final AcquisitionClient acquisitionClient = (AcquisitionClient) client;
- acquisitionClient.onAcquired(acquiredInfo, vendorCode);
-
- if (client instanceof AuthenticationClient) {
- final int userId = client.getTargetUserId();
- if (getLockoutMode(userId) == LockoutTracker.LOCKOUT_NONE) {
- mPerformanceTracker.incrementAcquireForUser(userId, client.isCryptoOperation());
- }
- }
- }
-
- protected void handleAuthenticated(BiometricAuthenticator.Identifier identifier,
- ArrayList<Byte> token) {
- final ClientMonitor client = mCurrentClient;
- if (!(client instanceof AuthenticationClient)) {
- final String clientName = client != null ? client.getClass().getSimpleName() : "null";
- Slog.e(getTag(), "handleAuthenticated for non-authentication client: " + clientName);
- return;
- }
-
- final AuthenticationClient authenticationClient = (AuthenticationClient) client;
- final boolean authenticated = identifier.getBiometricId() != 0;
-
- final int userId = authenticationClient.getTargetUserId();
- if (authenticationClient.isCryptoOperation()) {
- mPerformanceTracker.incrementCryptoAuthForUser(userId, authenticated);
- } else {
- mPerformanceTracker.incrementAuthForUser(userId, authenticated);
- }
-
- authenticationClient.onAuthenticated(identifier, authenticated, token);
- }
-
- protected void handleEnrollResult(BiometricAuthenticator.Identifier identifier,
- int remaining) {
- final ClientMonitor client = mCurrentClient;
- if (!(client instanceof EnrollClient)) {
- final String clientName = client != null ? client.getClass().getSimpleName() : "null";
- Slog.e(getTag(), "handleEnrollResult for non-enroll client: " + clientName);
- return;
- }
-
- final EnrollClient enrollClient = (EnrollClient) client;
- enrollClient.onEnrollResult(identifier, remaining);
- }
-
- protected void handleError(int error, int vendorCode) {
- final ClientMonitor client = mCurrentClient;
-
- if (DEBUG) Slog.v(getTag(), "handleError(client="
- + (client != null ? client.getOwnerString() : "null") + ", error = " + error + ")");
-
- if (!(client instanceof ErrorConsumer)) {
- Slog.e(getTag(), "error received for non-ErrorConsumer");
- return;
- }
-
- final ErrorConsumer errorConsumer = (ErrorConsumer) client;
- errorConsumer.onError(error, vendorCode);
-
- if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
- mHandler.removeCallbacks(mResetClientState);
- if (mPendingClient != null) {
- if (DEBUG) Slog.v(getTag(), "start pending client " +
- mPendingClient.getOwnerString());
- startClient(mPendingClient, false);
- mPendingClient = null;
- }
- }
- }
-
- protected void handleRemoved(BiometricAuthenticator.Identifier identifier,
- final int remaining) {
- if (DEBUG) Slog.w(getTag(), "Removed: fid=" + identifier.getBiometricId()
- + ", dev=" + identifier.getDeviceId()
- + ", rem=" + remaining);
-
- final ClientMonitor client = mCurrentClient;
- if (!(client instanceof RemovalConsumer)) {
- final String clientName = client != null ? client.getClass().getSimpleName() : "null";
- Slog.e(getTag(), "handleRemoved for non-removal consumer: " + clientName);
- return;
- }
-
- final RemovalConsumer removalConsumer = (RemovalConsumer) client;
- removalConsumer.onRemoved(identifier, remaining);
- }
-
- protected void handleEnumerate(BiometricAuthenticator.Identifier identifier, int remaining) {
- final ClientMonitor client = mCurrentClient;
- if (!(client instanceof EnumerateConsumer)) {
- final String clientName = client != null ? client.getClass().getSimpleName() : "null";
- Slog.e(getTag(), "handleEnumerate for non-enumerate consumer: "
- + clientName);
- return;
- }
-
- final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
- enumerateConsumer.onEnumerationResult(identifier, remaining);
- }
-
- /**
- * Calls from the Manager. These are still on the calling binder's thread.
- */
-
- protected void enrollInternal(EnrollClient<T> client, int userId) {
- if (hasReachedEnrollmentLimit(userId)) {
- return;
- }
-
- // Group ID is arbitrarily set to parent profile user ID. It just represents
- // the default biometrics for the user.
- if (!isCurrentUserOrProfile(userId)) {
- return;
- }
-
- mHandler.post(() -> {
- startClient(client, true /* initiatedByClient */);
- });
- }
-
- protected void cancelEnrollmentInternal(IBinder token) {
- mHandler.post(() -> {
- ClientMonitor client = mCurrentClient;
- if (client instanceof EnrollClient && client.getToken() == token) {
- if (DEBUG) Slog.v(getTag(), "Cancelling enrollment");
- ((EnrollClient) client).cancel();
- }
- });
- }
-
- protected void generateChallengeInternal(GenerateChallengeClient<T> client) {
- mHandler.post(() -> {
- startClient(client, true /* initiatedByClient */);
- });
- }
-
- protected void revokeChallengeInternal(RevokeChallengeClient<T> client) {
- mHandler.post(() -> {
- startClient(client, true /* initiatedByClient */);
- });
- }
-
- protected void authenticateInternal(AuthenticationClient<T> client, String opPackageName) {
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- final int callingUserId = UserHandle.getCallingUserId();
- authenticateInternal(client, opPackageName, callingUid, callingPid, callingUserId);
- }
-
- protected void authenticateInternal(AuthenticationClient<T> client,
- String opPackageName, int callingUid, int callingPid, int callingUserId) {
- if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
- callingUserId)) {
- if (DEBUG) Slog.v(getTag(), "authenticate(): reject " + opPackageName);
- return;
- }
-
- mHandler.post(() -> {
- startAuthentication(client, opPackageName);
- });
- }
-
- protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName) {
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- final int callingUserId = UserHandle.getCallingUserId();
- cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, callingUserId,
- true /* fromClient */);
- }
-
- protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName,
- int callingUid, int callingPid, int callingUserId, boolean fromClient) {
-
- if (DEBUG) Slog.v(getTag(), "cancelAuthentication(" + opPackageName + ")");
- if (fromClient) {
- // Only check this if cancel was called from the client (app). If cancel was called
- // from BiometricService, it means the dialog was dismissed due to user interaction.
- if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
- callingUserId)) {
- if (DEBUG) {
- Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName);
- }
- return;
- }
- }
-
- mHandler.post(() -> {
- ClientMonitor client = mCurrentClient;
- if (client instanceof AuthenticationClient) {
- if (client.getToken() == token || !fromClient) {
- if (DEBUG) Slog.v(getTag(), "Stopping client " + client.getOwnerString()
- + ", fromClient: " + fromClient);
- // If cancel was from BiometricService, it means the dialog was dismissed
- // and authentication should be canceled.
- ((AuthenticationClient) client).cancel();
- } else {
- if (DEBUG) Slog.v(getTag(), "Can't stop client " + client.getOwnerString()
- + " since tokens don't match. fromClient: " + fromClient);
- }
- } else if (client != null) {
- if (DEBUG) Slog.v(getTag(), "Can't cancel non-authenticating client "
- + client.getOwnerString());
- }
- });
- }
-
- protected void removeInternal(RemovalClient<T> client) {
- mHandler.post(() -> {
- startClient(client, true /* initiatedByClient */);
- });
- }
-
- protected void cleanupInternal(
- InternalCleanupClient<? extends BiometricAuthenticator.Identifier, T> client) {
- mHandler.post(() -> {
- if (DEBUG) {
- Slog.v(getTag(), "Cleaning up templates for user("
- + client.getTargetUserId() + ")");
- }
- startClient(client, true /* initiatedByClient */);
- });
- }
-
- // Should be done on a handler thread - not on the Binder's thread.
- private void startAuthentication(AuthenticationClient<T> client, String opPackageName) {
- if (DEBUG) Slog.v(getTag(), "startAuthentication(" + opPackageName + ")");
-
- startClient(client, true /* initiatedByClient */);
- }
-
- /**
- * Helper methods.
- */
-
- /**
- * @param opPackageName name of package for caller
- * @param requireForeground only allow this call while app is in the foreground
- * @return true if caller can use the biometric API
- */
- protected boolean canUseBiometric(String opPackageName, boolean requireForeground, int uid,
- int pid, int userId) {
- checkUseBiometricPermission();
-
-
- if (Binder.getCallingUid() == Process.SYSTEM_UID) {
- return true; // System process (BiometricService, etc) is always allowed
- }
- if (isKeyguard(opPackageName)) {
- return true; // Keyguard is always allowed
- }
- if (!isCurrentUserOrProfile(userId)) {
- Slog.w(getTag(), "Rejecting " + opPackageName + "; not a current user or profile");
- return false;
- }
- if (!checkAppOps(uid, opPackageName)) {
- Slog.w(getTag(), "Rejecting " + opPackageName + "; permission denied");
- return false;
- }
-
- if (requireForeground && !(isForegroundActivity(uid, pid) || isCurrentClient(
- opPackageName))) {
- Slog.w(getTag(), "Rejecting " + opPackageName + "; not in foreground");
- return false;
- }
- return true;
- }
-
- /**
- * @param opPackageName package of the caller
- * @return true if this is the same client currently using the biometric
- */
- private boolean isCurrentClient(String opPackageName) {
- return mCurrentClient != null && mCurrentClient.getOwnerString().equals(opPackageName);
- }
-
- /**
- * @return true if this is keyguard package
- */
- public boolean isKeyguard(String clientPackage) {
- return mKeyguardPackage.equals(clientPackage);
- }
-
- private boolean isForegroundActivity(int uid, int pid) {
- try {
- final List<ActivityManager.RunningAppProcessInfo> procs =
- ActivityManager.getService().getRunningAppProcesses();
- if (procs == null) {
- Slog.e(getTag(), "Processes null, defaulting to true");
- return true;
- }
-
- int N = procs.size();
- for (int i = 0; i < N; i++) {
- ActivityManager.RunningAppProcessInfo proc = procs.get(i);
- if (proc.pid == pid && proc.uid == uid
- && proc.importance <= IMPORTANCE_FOREGROUND_SERVICE) {
- return true;
- }
- }
- } catch (RemoteException e) {
- Slog.w(getTag(), "am.getRunningAppProcesses() failed");
- }
- return false;
- }
-
- /**
- * Calls the HAL to switch states to the new task. If there's already a current task,
- * it calls cancel() and sets mPendingClient to begin when the current task finishes
- * ({@link BiometricConstants#BIOMETRIC_ERROR_CANCELED}).
- *
- * @param newClient the new client that wants to connect
- * @param initiatedByClient true for authenticate, remove and enroll
- */
- @VisibleForTesting
- protected void startClient(ClientMonitor<T> newClient, boolean initiatedByClient) {
- ClientMonitor currentClient = mCurrentClient;
- if (currentClient != null) {
- if (DEBUG) Slog.v(getTag(), "request stop current client " +
- currentClient.getOwnerString());
- if (currentClient instanceof InternalCleanupClient) {
- // This condition means we're currently running internal diagnostics to
- // remove extra templates in the hardware and/or the software
- // TODO: design an escape hatch in case client never finishes
- if (newClient != null) {
- Slog.w(getTag(), "Internal cleanup in progress but trying to start client "
- + newClient.getClass().getSuperclass().getSimpleName()
- + "(" + newClient.getOwnerString() + ")"
- + ", initiatedByClient = " + initiatedByClient);
- }
- } else if (currentClient instanceof Cancellable) {
- ((Cancellable) currentClient).cancel();
-
- // Only post the reset runnable for non-cleanup clients. Cleanup clients should
- // never be forcibly stopped since they ensure synchronization between HAL and
- // framework. Thus, we should instead just start the pending client once cleanup
- // finishes instead of using the reset runnable.
- mHandler.removeCallbacks(mResetClientState);
- mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);
- }
- mPendingClient = newClient;
- } else if (newClient != null) {
- // For BiometricPrompt clients, do not start until
- // <Biometric>Service#startPreparedClient is called. BiometricService waits until all
- // modalities are ready before initiating authentication.
- if (newClient instanceof AuthenticationClient) {
- AuthenticationClient client = (AuthenticationClient) newClient;
- if (client.isBiometricPrompt()) {
- if (DEBUG) Slog.v(getTag(), "Returning cookie: " + client.getCookie());
- mCurrentClient = newClient;
- if (mBiometricService == null) {
- mBiometricService = IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE));
- }
- try {
- mBiometricService.onReadyForAuthentication(client.getCookie());
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception", e);
- }
- return;
- }
- }
-
- // We are not a BiometricPrompt client, start the client immediately
- mCurrentClient = newClient;
- startCurrentClient(mCurrentClient.getCookie());
- }
- }
-
- protected void startCurrentClient(int cookie) {
- if (mCurrentClient == null) {
- Slog.e(getTag(), "Trying to start null client!");
- return;
- }
-
- if (DEBUG) Slog.v(getTag(), "Starting client "
- + mCurrentClient.getClass().getSimpleName()
- + "(" + mCurrentClient.getOwnerString() + ")"
- + " targetUserId: " + mCurrentClient.getTargetUserId()
- + " currentUserId: " + mCurrentUserId
- + " cookie: " + cookie + "/" + mCurrentClient.getCookie());
-
- if (cookie != mCurrentClient.getCookie()) {
- Slog.e(getTag(), "Mismatched cookie");
- return;
- }
-
- final T daemon = mCurrentClient.getFreshDaemon();
- if (daemon == null) {
- Slog.e(getTag(), "Daemon null, unable to start: "
- + mCurrentClient.getClass().getSimpleName());
- mCurrentClient.unableToStart();
- mCurrentClient = null;
- return;
- }
-
- mCurrentClient.start(mClientFinishCallback);
- notifyClientActiveCallbacks(true);
- }
-
- protected void removeClient(ClientMonitor client) {
- if (client != null) {
- client.destroy();
- if (client != mCurrentClient && mCurrentClient != null) {
- Slog.w(getTag(), "Unexpected client: " + client.getOwnerString() + "expected: "
- + mCurrentClient.getOwnerString());
- }
- }
- if (mCurrentClient != null) {
- if (DEBUG) Slog.v(getTag(), "Done with client: "
- + mCurrentClient.getClass().getSimpleName()
- + "(" + mCurrentClient.getOwnerString() + ")");
- mCurrentClient = null;
- }
- if (mPendingClient == null) {
- notifyClientActiveCallbacks(false);
- }
- }
-
- /**
- * Populates existing authenticator ids. To be used only during the start of the service.
- */
- protected void loadAuthenticatorIds() {
- // This operation can be expensive, so keep track of the elapsed time. Might need to move to
- // background if it takes too long.
- long t = System.currentTimeMillis();
- mAuthenticatorIds.clear();
- for (UserInfo user : UserManager.get(getContext()).getUsers(true /* excludeDying */)) {
- int userId = user.id;
- if (!mAuthenticatorIds.containsKey(userId)) {
- updateActiveGroup(userId);
- }
- }
-
- t = System.currentTimeMillis() - t;
- if (t > 1000) {
- Slog.w(getTag(), "loadAuthenticatorIds() taking too long: " + t + "ms");
- }
- }
-
- protected boolean isRestricted() {
- // Only give privileged apps (like Settings) access to biometric info
- final boolean restricted = !hasPermission(getManageBiometricPermission());
- return restricted;
- }
-
- protected boolean hasPermission(String permission) {
- return getContext().checkCallingOrSelfPermission(permission)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- protected void checkPermission(String permission) {
- getContext().enforceCallingOrSelfPermission(permission,
- "Must have " + permission + " permission.");
- }
-
- protected boolean isCurrentUserOrProfile(int userId) {
- UserManager um = UserManager.get(mContext);
- if (um == null) {
- Slog.e(getTag(), "Unable to acquire UserManager");
- return false;
- }
-
- final long token = Binder.clearCallingIdentity();
- try {
- // Allow current user or profiles of the current user...
- for (int profileId : um.getEnabledProfileIds(ActivityManager.getCurrentUser())) {
- if (profileId == userId) {
- return true;
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- return false;
- }
-
- /**
- * @return authenticator id for the calling user
- */
- protected long getAuthenticatorId(int callingUserId) {
- return mAuthenticatorIds.getOrDefault(callingUserId, 0L);
- }
-
- /**
- * This method should be called upon connection to the daemon, and when user switches.
- */
- protected abstract void doTemplateCleanupForUser(int userId);
-
- /**
- * This method is called when the user switches. Implementations should probably notify the
- * HAL.
- */
- protected void handleUserSwitching(int userId) {
- if (getCurrentClient() instanceof InternalCleanupClient) {
- Slog.w(getTag(), "User switched while performing cleanup");
- }
- updateActiveGroup(userId);
- doTemplateCleanupForUser(userId);
- }
-
- private void listenForUserSwitches() {
- try {
- ActivityManager.getService().registerUserSwitchObserver(
- new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- mHandler.obtainMessage(MSG_USER_SWITCHING, newUserId, 0 /* unused */)
- .sendToTarget();
- }
- }, getTag());
- } catch (RemoteException e) {
- Slog.w(getTag(), "Failed to listen for user switching event" ,e);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
index 3f301cc..8b27781 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
@@ -34,7 +34,10 @@
public abstract class ClientMonitor<T> extends LoggableMonitor implements IBinder.DeathRecipient {
private static final String TAG = "Biometrics/ClientMonitor";
- protected static final boolean DEBUG = BiometricServiceBase.DEBUG;
+ protected static final boolean DEBUG = true;
+
+ // Counter used to distinguish between ClientMonitor instances to help debugging.
+ private static int sCount = 0;
/**
* Interface that ClientMonitor holders should use to receive callbacks.
@@ -49,7 +52,7 @@
* @param clientMonitor Reference of the ClientMonitor that finished.
* @param success True if the operation completed successfully.
*/
- void onClientFinished(ClientMonitor clientMonitor, boolean success);
+ void onClientFinished(ClientMonitor<?> clientMonitor, boolean success);
}
/**
@@ -62,6 +65,7 @@
T getDaemon();
}
+ private final int mSequentialId;
@NonNull private final Context mContext;
@NonNull protected final LazyDaemon<T> mLazyDaemon;
private final int mTargetUserId;
@@ -95,6 +99,7 @@
@NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction,
int statsClient) {
super(statsModality, statsAction, statsClient);
+ mSequentialId = sCount++;
mContext = context;
mLazyDaemon = lazyDaemon;
mToken = token;
@@ -163,9 +168,9 @@
// TODO(b/157790417): Move this to the scheduler
void binderDiedInternal(boolean clearListener) {
// If the current client dies we should cancel the current operation.
- if (this instanceof Cancellable) {
+ if (this instanceof Interruptable) {
Slog.e(TAG, "Binder died, cancelling client");
- ((Cancellable) this).cancel();
+ ((Interruptable) this).cancel();
}
mToken = null;
if (clearListener) {
@@ -200,4 +205,12 @@
public final T getFreshDaemon() {
return mLazyDaemon.getDaemon();
}
+
+ @Override
+ public String toString() {
+ return "{[" + mSequentialId + "] "
+ + this.getClass().getSimpleName()
+ + ", " + getOwnerString()
+ + ", " + getCookie() + "}";
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index a3d9677..163f29e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -35,12 +35,17 @@
protected final byte[] mHardwareAuthToken;
protected final int mTimeoutSec;
- private final BiometricUtils mBiometricUtils;
+ protected final BiometricUtils mBiometricUtils;
private final boolean mShouldVibrate;
private long mEnrollmentStartTimeMs;
private boolean mAlreadyCancelled;
+ /**
+ * @return true if the user has already enrolled the maximum number of templates.
+ */
+ protected abstract boolean hasReachedEnrollmentLimit();
+
public EnrollClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils,
@@ -82,6 +87,12 @@
public void start(@NonNull FinishCallback finishCallback) {
super.start(finishCallback);
+ if (hasReachedEnrollmentLimit()) {
+ Slog.e(TAG, "Reached enrollment limit");
+ finishCallback.onClientFinished(this, false /* success */);
+ return;
+ }
+
mEnrollmentStartTimeMs = System.currentTimeMillis();
startHalOperation();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/ErrorConsumer.java b/services/core/java/com/android/server/biometrics/sensors/ErrorConsumer.java
deleted file mode 100644
index 0c3b0b7..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/ErrorConsumer.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors;
-
-/**
- * Interface that {@link ClientMonitor} subclasses eligible/interested in error callbacks should
- * implement.
- */
-public interface ErrorConsumer {
- /**
- * @param errorCode defined by the HIDL interface
- * @param vendorCode defined by the vendor
- */
- void onError(int errorCode, int vendorCode);
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
new file mode 100644
index 0000000..35d9177
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 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.biometrics.sensors;
+
+import android.annotation.NonNull;
+
+/**
+ * Interface that {@link ClientMonitor} subclasses eligible/interested in error callbacks should
+ * implement.
+ */
+public interface Interruptable {
+ /**
+ * Requests to end the ClientMonitor's lifecycle.
+ */
+ void cancel();
+
+ /**
+ * Notifies the client of errors from the HAL.
+ * @param errorCode defined by the HIDL interface
+ * @param vendorCode defined by the vendor
+ */
+ void onError(int errorCode, int vendorCode);
+
+ /**
+ * Notifies the client that it needs to finish before
+ * {@link ClientMonitor#start(ClientMonitor.FinishCallback)} was invoked. This usually happens
+ * if the client is still waiting in the pending queue and got notified that a subsequent
+ * operation is preempting it.
+ * @param finishCallback invoked when the operation is completed.
+ */
+ void cancelWithoutStarting(@NonNull ClientMonitor.FinishCallback finishCallback);
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutResetTracker.java b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
similarity index 87%
rename from services/core/java/com/android/server/biometrics/sensors/LockoutResetTracker.java
rename to services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
index 9bb5c6e..f4997d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutResetTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
@@ -29,9 +29,10 @@
/**
* Allows clients (such as keyguard) to register for notifications on when biometric lockout
- * ends.
+ * ends. This class keeps track of all client callbacks. Individual sensors should notify this
+ * when lockout for a specific sensor has been reset.
*/
-public class LockoutResetTracker implements IBinder.DeathRecipient {
+public class LockoutResetDispatcher implements IBinder.DeathRecipient {
private static final String TAG = "LockoutResetTracker";
@@ -54,11 +55,11 @@
"LockoutResetMonitor:SendLockoutReset");
}
- void sendLockoutReset() {
+ void sendLockoutReset(int sensorId) {
if (mCallback != null) {
try {
mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
- mCallback.onLockoutReset(new IRemoteCallback.Stub() {
+ mCallback.onLockoutReset(sensorId, new IRemoteCallback.Stub() {
@Override
public void sendResult(Bundle data) {
releaseWakelock();
@@ -78,7 +79,7 @@
}
}
- public LockoutResetTracker(Context context) {
+ public LockoutResetDispatcher(Context context) {
mContext = context;
mClientCallbacks = new ArrayList<>();
}
@@ -114,9 +115,9 @@
}
}
- public void notifyLockoutResetCallbacks() {
+ public void notifyLockoutResetCallbacks(int sensorId) {
for (ClientCallback callback : mClientCallbacks) {
- callback.sendLockoutReset();
+ callback.sendLockoutReset(sensorId);
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
index a7c63f7..1a4216f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
@@ -39,10 +39,6 @@
private final int mStatsClient;
private long mFirstAcquireTimeMs;
- protected long getFirstAcquireTimeMs() {
- return mFirstAcquireTimeMs;
- }
-
/**
* Only valid for AuthenticationClient.
* @return true if the client is authenticating for a crypto operation.
@@ -62,6 +58,12 @@
mStatsClient = statsClient;
}
+ private boolean isAnyFieldUnknown() {
+ return mStatsModality == BiometricsProtoEnums.MODALITY_UNKNOWN
+ || mStatsAction == BiometricsProtoEnums.ACTION_UNKNOWN
+ || mStatsClient == BiometricsProtoEnums.CLIENT_UNKNOWN;
+ }
+
protected final void logOnAcquired(Context context, int acquiredInfo, int vendorCode,
int targetUserId) {
@@ -86,6 +88,11 @@
+ ", AcquiredInfo: " + acquiredInfo
+ ", VendorCode: " + vendorCode);
}
+
+ if (isAnyFieldUnknown()) {
+ return;
+ }
+
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ACQUIRED,
mStatsModality,
targetUserId,
@@ -114,6 +121,11 @@
} else {
Slog.v(TAG, "Error latency: " + latency);
}
+
+ if (isAnyFieldUnknown()) {
+ return;
+ }
+
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED,
mStatsModality,
targetUserId,
@@ -157,6 +169,10 @@
Slog.v(TAG, "Authentication latency: " + latency);
}
+ if (isAnyFieldUnknown()) {
+ return;
+ }
+
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
mStatsModality,
targetUserId,
@@ -179,6 +195,10 @@
Slog.v(TAG, "Enroll latency: " + latency);
}
+ if (isAnyFieldUnknown()) {
+ return;
+ }
+
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED,
mStatsModality,
targetUserId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java b/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
index fa490ee..9fdba4b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
@@ -107,7 +107,7 @@
mAllUsersInfo.get(userId).mPermanentLockout++;
}
- void incrementHALDeathCount() {
+ public void incrementHALDeathCount() {
mHALDeathCount++;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
new file mode 100644
index 0000000..c4ea0a8
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
@@ -0,0 +1,649 @@
+/*
+ * Copyright (C) 2020 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.biometrics.sensors.face;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.NotificationManager;
+import android.app.SynchronousUserSwitchObserver;
+import android.app.UserSwitchObserver;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
+import android.hardware.face.Face;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IHwBinder;
+import android.os.Looper;
+import android.os.NativeHandle;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.ClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.EnumerateConsumer;
+import com.android.server.biometrics.sensors.Interruptable;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.PerformanceTracker;
+import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUpdateActiveUserClient;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Supports a single instance of the {@link android.hardware.biometrics.face.V1_0} or
+ * its extended minor versions.
+ */
+class Face10 implements IHwBinder.DeathRecipient {
+
+ private static final String TAG = "Face10";
+ private static final int ENROLL_TIMEOUT_SEC = 75;
+ static final String NOTIFICATION_TAG = "FaceService";
+ static final int NOTIFICATION_ID = 1;
+
+ @NonNull private final Context mContext;
+ @NonNull private final BiometricScheduler mScheduler;
+ @NonNull private final Handler mHandler;
+ @NonNull private final ClientMonitor.LazyDaemon<IBiometricsFace> mLazyDaemon;
+ @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
+ @NonNull private final LockoutHalImpl mLockoutTracker;
+ @NonNull private final UsageStats mUsageStats;
+ @NonNull private NotificationManager mNotificationManager;
+ private final int mSensorId;
+ @NonNull private final Map<Integer, Long> mAuthenticatorIds;
+
+ @Nullable private IBiometricsFace mDaemon;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+
+ private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(newUserId);
+ }
+ };
+
+ private final IBiometricsFaceClientCallback mDaemonCallback =
+ new IBiometricsFaceClientCallback.Stub() {
+ @Override
+ public void onEnrollResult(long deviceId, int faceId, int userId, int remaining) {
+ mHandler.post(() -> {
+ final CharSequence name = FaceUtils.getInstance()
+ .getUniqueName(mContext, userId);
+ final Face face = new Face(name, faceId, deviceId);
+
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FaceEnrollClient)) {
+ Slog.e(TAG, "onEnrollResult for non-enroll client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FaceEnrollClient enrollClient = (FaceEnrollClient) client;
+ enrollClient.onEnrollResult(face, remaining);
+ });
+ }
+
+ @Override
+ public void onAuthenticated(long deviceId, int faceId, int userId, ArrayList<Byte> token) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FaceAuthenticationClient)) {
+ Slog.e(TAG, "onAuthenticated for non-authentication client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FaceAuthenticationClient authenticationClient =
+ (FaceAuthenticationClient) client;
+ final boolean authenticated = faceId != 0;
+ final Face face = new Face("", faceId, deviceId);
+ authenticationClient.onAuthenticated(face, authenticated, token);
+ });
+ }
+
+ @Override
+ public void onAcquired(long deviceId, int userId, int acquiredInfo, int vendorCode) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof AcquisitionClient)) {
+ Slog.e(TAG, "onAcquired for non-acquire client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
+ acquisitionClient.onAcquired(acquiredInfo, vendorCode);
+ });
+ }
+
+ @Override
+ public void onError(long deviceId, int userId, int error, int vendorCode) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ Slog.d(TAG, "handleError"
+ + ", client: " + (client != null ? client.getOwnerString() : null)
+ + ", error: " + error
+ + ", vendorCode: " + vendorCode);
+ if (!(client instanceof Interruptable)) {
+ Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(client));
+ return;
+ }
+
+ final Interruptable interruptable = (Interruptable) client;
+ interruptable.onError(error, vendorCode);
+
+ if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
+ Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
+ mDaemon = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }
+ });
+ }
+
+ @Override
+ public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof RemovalConsumer)) {
+ Slog.e(TAG, "onRemoved for non-removal consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final RemovalConsumer removalConsumer = (RemovalConsumer) client;
+
+ if (!removed.isEmpty()) {
+ // Convert to old fingerprint-like behavior, where remove() receives one removal
+ // at a time. This way, remove can share some more common code.
+ for (int i = 0; i < removed.size(); i++) {
+ final int id = removed.get(i);
+ final Face face = new Face("", id, deviceId);
+ final int remaining = removed.size() - i - 1;
+ Slog.d(TAG, "Removed, faceId: " + id + ", remaining: " + remaining);
+ removalConsumer.onRemoved(face, remaining);
+ }
+ } else {
+ final Face face = new Face("", 0 /* identifier */, deviceId);
+ removalConsumer.onRemoved(face, 0 /* remaining */);
+ }
+
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, UserHandle.USER_CURRENT);
+ });
+ }
+
+ @Override
+ public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof EnumerateConsumer)) {
+ Slog.e(TAG, "onEnumerate for non-enumerate consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
+
+ if (!faceIds.isEmpty()) {
+ // Convert to old fingerprint-like behavior, where enumerate() receives one
+ // template at a time. This way, enumerate can share some more common code.
+ for (int i = 0; i < faceIds.size(); i++) {
+ final Face face = new Face("", faceIds.get(i), deviceId);
+ enumerateConsumer.onEnumerationResult(face, faceIds.size() - i - 1);
+ }
+ } else {
+ // For face, the HIDL contract is to receive an empty list when there are no
+ // templates enrolled. Send a null identifier since we don't consume them
+ // anywhere, and send remaining == 0 so this code can be shared with
+ // Fingerprint@2.1
+ enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
+ }
+ });
+ }
+
+ @Override
+ public void onLockoutChanged(long duration) {
+ mHandler.post(() -> {
+ Slog.d(TAG, "onLockoutChanged: " + duration);
+ final @LockoutTracker.LockoutMode int lockoutMode;
+ if (duration == 0) {
+ lockoutMode = LockoutTracker.LOCKOUT_NONE;
+ } else if (duration == -1 || duration == Long.MAX_VALUE) {
+ lockoutMode = LockoutTracker.LOCKOUT_PERMANENT;
+ } else {
+ lockoutMode = LockoutTracker.LOCKOUT_TIMED;
+ }
+
+ mLockoutTracker.setCurrentUserLockoutMode(lockoutMode);
+
+ if (duration == 0) {
+ mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
+ }
+ });
+ }
+ };
+
+ Face10(@NonNull Context context, int sensorId,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+ mContext = context;
+ mSensorId = sensorId;
+ mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */);
+ mHandler = new Handler(Looper.getMainLooper());
+ mUsageStats = new UsageStats(context);
+ mAuthenticatorIds = new HashMap<>();
+ mLazyDaemon = Face10.this::getDaemon;
+ mNotificationManager = mContext.getSystemService(NotificationManager.class);
+ mLockoutTracker = new LockoutHalImpl();
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+
+ try {
+ ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to register user switch observer");
+ }
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ Slog.e(TAG, "HAL died");
+ mHandler.post(() -> {
+ PerformanceTracker.getInstanceForSensorId(mSensorId)
+ .incrementHALDeathCount();
+ mDaemon = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (client instanceof Interruptable) {
+ Slog.e(TAG, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
+ final Interruptable interruptable = (Interruptable) client;
+ interruptable.onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+
+ mScheduler.recordCrashState();
+
+ FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
+ BiometricsProtoEnums.MODALITY_FACE,
+ BiometricsProtoEnums.ISSUE_HAL_DEATH);
+ }
+ });
+ }
+
+ private synchronized IBiometricsFace getDaemon() {
+ if (mDaemon != null) {
+ return mDaemon;
+ }
+
+ Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
+ + mScheduler.getCurrentClient());
+
+ try {
+ mDaemon = IBiometricsFace.getService();
+ } catch (java.util.NoSuchElementException e) {
+ // Service doesn't exist or cannot be opened.
+ Slog.w(TAG, "NoSuchElementException", e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get face HAL", e);
+ }
+
+ if (mDaemon == null) {
+ Slog.w(TAG, "Face HAL not available");
+ return null;
+ }
+
+ mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
+
+ // HAL ID for these HIDL versions are only used to determine if callbacks have been
+ // successfully set.
+ long halId = 0;
+ try {
+ halId = mDaemon.setCallback(mDaemonCallback).value;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set callback for face HAL", e);
+ mDaemon = null;
+ }
+
+ Slog.d(TAG, "Face HAL ready, HAL ID: " + halId);
+ if (halId != 0) {
+ scheduleLoadAuthenticatorIds();
+ scheduleInternalCleanup(ActivityManager.getCurrentUser());
+ } else {
+ Slog.e(TAG, "Unable to set callback");
+ mDaemon = null;
+ }
+
+ return mDaemon;
+ }
+
+ @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) {
+ return mLockoutTracker.getLockoutModeForUser(userId);
+ }
+
+ private void scheduleLoadAuthenticatorIds() {
+ // Note that this can be performed on the scheduler (as opposed to being done immediately
+ // when the HAL is (re)loaded, since
+ // 1) If this is truly the first time it's being performed (e.g. system has just started),
+ // this will be run very early and way before any applications need to generate keys.
+ // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
+ // just been reloaded), the framework already has a cache of the authenticatorIds. This
+ // is safe because authenticatorIds only change when A) new template has been enrolled,
+ // or B) all templates are removed.
+ mHandler.post(() -> {
+ for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
+ final int targetUserId = user.id;
+ if (!mAuthenticatorIds.containsKey(targetUserId)) {
+ scheduleUpdateActiveUserWithoutHandler(targetUserId);
+ }
+ }
+ });
+ }
+
+ /**
+ * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the
+ * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
+ * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
+ * this operation on the same lambda/runnable as those operations so that the ordering is
+ * correct.
+ */
+ private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
+ final boolean hasEnrolled = !getEnrolledFaces(targetUserId).isEmpty();
+ final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
+ mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId, mCurrentUserId,
+ hasEnrolled, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, (clientMonitor, success) -> {
+ if (success) {
+ mCurrentUserId = targetUserId;
+ }
+ });
+ }
+
+ void scheduleResetLockout(int userId, @NonNull byte[] hardwareAuthToken) {
+ mHandler.post(() -> {
+ if (getEnrolledFaces(userId).isEmpty()) {
+ Slog.w(TAG, "Ignoring lockout reset, no templates enrolled for user: " + userId);
+ return;
+ }
+
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
+ mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
+ hardwareAuthToken);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void scheduleSetFeature(@NonNull IBinder token, int userId, int feature, boolean enabled,
+ @NonNull byte[] hardwareAuthToken, @NonNull IFaceServiceReceiver receiver,
+ @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ final List<Face> faces = getEnrolledFaces(userId);
+ if (faces.isEmpty()) {
+ Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId);
+ return;
+ }
+
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final int faceId = faces.get(0).getBiometricId();
+ final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
+ opPackageName, mSensorId, feature, enabled, hardwareAuthToken, faceId);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void scheduleGetFeature(@NonNull IBinder token, int userId, int feature,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ final List<Face> faces = getEnrolledFaces(userId);
+ if (faces.isEmpty()) {
+ Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId);
+ return;
+ }
+
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final int faceId = faces.get(0).getBiometricId();
+ final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
+ opPackageName, mSensorId, feature, faceId);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void scheduleGenerateChallenge(@NonNull IBinder token, @NonNull IFaceServiceReceiver receiver,
+ @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), opPackageName,
+ mSensorId);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String owner) {
+ mHandler.post(() -> {
+ final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
+ mLazyDaemon, token, owner, mSensorId);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void scheduleEnroll(@NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName,
+ @NonNull int[] disabledFeatures, @Nullable NativeHandle surfaceHandle) {
+ mHandler.post(() -> {
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ mNotificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_ID,
+ UserHandle.CURRENT);
+
+ final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
+ new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
+ opPackageName, FaceUtils.getInstance(), disabledFeatures, ENROLL_TIMEOUT_SEC,
+ surfaceHandle, mSensorId);
+
+ mScheduler.scheduleClientMonitor(client, ((clientMonitor, success) -> {
+ if (success) {
+ // Update authenticatorIds
+ scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
+ }
+ }));
+ });
+ }
+
+ void cancelEnrollment(@NonNull IBinder token) {
+ mHandler.post(() -> {
+ mScheduler.cancelEnrollment(token);
+ });
+ }
+
+ void scheduleAuthenticate(@NonNull IBinder token, long operationId, int userId, int cookie,
+ @NonNull ClientMonitorCallbackConverter receiver, @NonNull String opPackageName,
+ boolean restricted, int statsClient) {
+ mHandler.post(() -> {
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
+ final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
+ mLazyDaemon, token, receiver, userId, operationId, restricted, opPackageName,
+ cookie, false /* requireConfirmation */, mSensorId, isStrongBiometric,
+ statsClient, mLockoutTracker, mUsageStats);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void startPreparedClient(int cookie) {
+ mHandler.post(() -> {
+ mScheduler.startPreparedClient(cookie);
+ });
+ }
+
+ void cancelAuthentication(@NonNull IBinder token) {
+ mHandler.post(() -> {
+ mScheduler.cancelAuthentication(token);
+ });
+ }
+
+ void scheduleRemove(@NonNull IBinder token, int faceId, int userId,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
+ new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
+ FaceUtils.getInstance(), mSensorId, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ private void scheduleInternalCleanup(int userId) {
+ mHandler.post(() -> {
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final List<Face> enrolledList = getEnrolledFaces(userId);
+ final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
+ mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, enrolledList,
+ FaceUtils.getInstance(), mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ boolean isHardwareDetected() {
+ final IBiometricsFace daemon = getDaemon();
+ return daemon != null;
+ }
+
+ List<Face> getEnrolledFaces(int userId) {
+ return FaceUtils.getInstance().getBiometricsForUser(mContext, userId);
+ }
+
+ long getAuthenticatorId(int userId) {
+ return mAuthenticatorIds.get(userId);
+ }
+
+ public void dump(@NonNull PrintWriter pw) {
+ PerformanceTracker performanceTracker =
+ PerformanceTracker.getInstanceForSensorId(mSensorId);
+
+ JSONObject dump = new JSONObject();
+ try {
+ dump.put("service", "Face Manager");
+
+ JSONArray sets = new JSONArray();
+ for (UserInfo user : UserManager.get(mContext).getUsers()) {
+ final int userId = user.getUserHandle().getIdentifier();
+ final int N = FaceUtils.getInstance().getBiometricsForUser(mContext, userId).size();
+ JSONObject set = new JSONObject();
+ set.put("id", userId);
+ set.put("count", N);
+ set.put("accept", performanceTracker.getAcceptForUser(userId));
+ set.put("reject", performanceTracker.getRejectForUser(userId));
+ set.put("acquire", performanceTracker.getAcquireForUser(userId));
+ set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
+ set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
+ // cryptoStats measures statistics about secure face transactions
+ // (e.g. to unlock password storage, make secure purchases, etc.)
+ set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
+ set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
+ set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
+ sets.put(set);
+ }
+
+ dump.put("prints", sets);
+ } catch (JSONException e) {
+ Slog.e(TAG, "dump formatting failure", e);
+ }
+ pw.println(dump);
+ pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
+
+ mUsageStats.print(pw);
+ }
+
+ public void dumpHal(@NonNull FileDescriptor fd, @NonNull String[] args) {
+ // WARNING: CDD restricts image data from leaving TEE unencrypted on
+ // production devices:
+ // [C-1-10] MUST not allow unencrypted access to identifiable biometric
+ // data or any data derived from it (such as embeddings) to the
+ // Application Processor outside the context of the TEE.
+ // As such, this API should only be enabled for testing purposes on
+ // engineering and userdebug builds. All modules in the software stack
+ // MUST enforce final build products do NOT have this functionality.
+ // Additionally, the following check MUST NOT be removed.
+ if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
+ return;
+ }
+
+ // Additionally, this flag allows turning off face for a device
+ // (either permanently through the build or on an individual device).
+ if (SystemProperties.getBoolean("ro.face.disable_debug_data", false)
+ || SystemProperties.getBoolean("persist.face.disable_debug_data", false)) {
+ return;
+ }
+
+ // The debug method takes two file descriptors. The first is for text
+ // output, which we will drop. The second is for binary data, which
+ // will be the protobuf data.
+ final IBiometricsFace daemon = getDaemon();
+ if (daemon != null) {
+ FileOutputStream devnull = null;
+ try {
+ devnull = new FileOutputStream("/dev/null");
+ final NativeHandle handle = new NativeHandle(
+ new FileDescriptor[] { devnull.getFD(), fd },
+ new int[0], false);
+ daemon.debug(handle, new ArrayList<String>(Arrays.asList(args)));
+ } catch (IOException | RemoteException ex) {
+ Slog.d(TAG, "error while reading face debugging data", ex);
+ } finally {
+ if (devnull != null) {
+ try {
+ devnull.close();
+ } catch (IOException ex) {
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
index 118cadc..21bda74 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticationClient.java
@@ -21,7 +21,6 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.TaskStackListener;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -67,11 +66,10 @@
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
boolean isStrongBiometric, int statsClient,
- @NonNull TaskStackListener taskStackListener,
@NonNull LockoutTracker lockoutTracker, @NonNull UsageStats usageStats) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
- BiometricsProtoEnums.MODALITY_FACE, statsClient, taskStackListener,
+ BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
lockoutTracker);
mNotificationManager = context.getSystemService(NotificationManager.class);
mUsageStats = usageStats;
@@ -221,8 +219,8 @@
.build();
mNotificationManager.createNotificationChannel(channel);
- mNotificationManager.notifyAsUser(FaceService.NOTIFICATION_TAG,
- FaceService.NOTIFICATION_ID, notification,
+ mNotificationManager.notifyAsUser(Face10.NOTIFICATION_TAG,
+ Face10.NOTIFICATION_ID, notification,
UserHandle.CURRENT);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
index 4f9f46a..52a8226 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceEnrollClient.java
@@ -68,6 +68,19 @@
}
@Override
+ protected boolean hasReachedEnrollmentLimit() {
+ final int limit = getContext().getResources().getInteger(
+ com.android.internal.R.integer.config_faceMaxTemplatesPerUser);
+ final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
+ .size();
+ if (enrolled >= limit) {
+ Slog.w(TAG, "Too many faces registered, user: " + getTargetUserId());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
public void onAcquired(int acquireInfo, int vendorCode) {
final boolean shouldSend;
if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index a7f8220..3c597a9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -16,65 +16,31 @@
package com.android.server.biometrics.sensors.face;
-import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.app.NotificationManager;
+import android.annotation.NonNull;
import android.content.Context;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
import android.hardware.face.Face;
import android.hardware.face.IFaceService;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
-import android.os.Build;
-import android.os.Environment;
import android.os.IBinder;
import android.os.NativeHandle;
-import android.os.RemoteException;
-import android.os.SELinux;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Slog;
import android.view.Surface;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.DumpUtils;
-import com.android.server.SystemServerInitThreadPool;
-import com.android.server.biometrics.sensors.AuthenticationClient;
-import com.android.server.biometrics.sensors.BiometricServiceBase;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.ClientMonitor;
+import com.android.server.SystemService;
+import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.EnrollClient;
-import com.android.server.biometrics.sensors.GenerateChallengeClient;
-import com.android.server.biometrics.sensors.LockoutResetTracker;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.RemovalClient;
-import com.android.server.biometrics.sensors.RevokeChallengeClient;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -82,182 +48,116 @@
* A service to manage multiple clients that want to access the face HAL API.
* The service is responsible for maintaining a list of clients and dispatching all
* face-related events.
- *
- * @hide
*/
-public class FaceService extends BiometricServiceBase<IBiometricsFace> {
+public class FaceService extends SystemService {
protected static final String TAG = "FaceService";
- private static final boolean DEBUG = true;
- private static final String FACE_DATA_DIR = "facedata";
- private static final String ACTION_LOCKOUT_RESET =
- "com.android.server.biometrics.face.ACTION_LOCKOUT_RESET";
-
- static final String NOTIFICATION_TAG = "FaceService";
- static final int NOTIFICATION_ID = 1;
-
- private final LockoutResetTracker mLockoutResetTracker;
- private final ClientMonitor.LazyDaemon<IBiometricsFace> mLazyDaemon;
+ private Face10 mFace10;
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
/**
* Receives the incoming binder calls from FaceManager.
*/
private final class FaceServiceWrapper extends IFaceService.Stub {
- private static final int ENROLL_TIMEOUT_SEC = 75;
-
- /**
- * The following methods contain common code which is shared in biometrics/common.
- */
-
@Override // Binder call
public void generateChallenge(IBinder token, IFaceServiceReceiver receiver,
String opPackageName) {
- checkPermission(MANAGE_BIOMETRIC);
-
- final GenerateChallengeClient client = new FaceGenerateChallengeClient(getContext(),
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), opPackageName,
- getSensorId());
- generateChallengeInternal(client);
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+ mFace10.scheduleGenerateChallenge(token, receiver, opPackageName);
}
@Override // Binder call
public void revokeChallenge(IBinder token, String owner) {
- checkPermission(MANAGE_BIOMETRIC);
-
- final RevokeChallengeClient client = new FaceRevokeChallengeClient(getContext(),
- mLazyDaemon, token, owner, getSensorId());
-
- // TODO(b/137106905): Schedule binder calls in FaceService to avoid deadlocks.
- if (getCurrentClient() == null) {
- // if we aren't handling any other HIDL calls (mCurrentClient == null), revoke
- // the challenge right away.
- revokeChallengeInternal(client);
- } else {
- // postpone revoking the challenge until we finish processing the current HIDL
- // call.
- mPendingRevokeChallenge = client;
- }
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+ mFace10.scheduleRevokeChallenge(token, owner);
}
@Override // Binder call
public void enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
final IFaceServiceReceiver receiver, final String opPackageName,
final int[] disabledFeatures, Surface surface) {
- checkPermission(MANAGE_BIOMETRIC);
- updateActiveGroup(userId);
-
- mHandler.post(() -> {
- mNotificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_ID,
- UserHandle.CURRENT);
- });
-
- final EnrollClient client = new FaceEnrollClient(getContext(), mLazyDaemon, token,
- new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, getBiometricUtils(), disabledFeatures, ENROLL_TIMEOUT_SEC,
- convertSurfaceToNativeHandle(surface), getSensorId());
-
- enrollInternal(client, userId);
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+ mFace10.scheduleEnroll(token, hardwareAuthToken, userId, receiver, opPackageName,
+ disabledFeatures, convertSurfaceToNativeHandle(surface));
}
@Override // Binder call
public void enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken,
final IFaceServiceReceiver receiver, final String opPackageName,
final int[] disabledFeatures) {
- checkPermission(MANAGE_BIOMETRIC);
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
// TODO(b/145027036): Implement this.
}
@Override // Binder call
public void cancelEnrollment(final IBinder token) {
- checkPermission(MANAGE_BIOMETRIC);
- cancelEnrollmentInternal(token);
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+ mFace10.cancelEnrollment(token);
}
@Override // Binder call
- public void authenticate(final IBinder token, final long opId, int userId,
+ public void authenticate(final IBinder token, final long operationId, int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- updateActiveGroup(userId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- final boolean restricted = isRestricted();
- final int statsClient = isKeyguard(opPackageName) ? BiometricsProtoEnums.CLIENT_KEYGUARD
+ final boolean restricted = false; // Face APIs are private
+ final int statsClient = Utils.isKeyguard(getContext(), opPackageName)
+ ? BiometricsProtoEnums.CLIENT_KEYGUARD
: BiometricsProtoEnums.CLIENT_UNKNOWN;
- final AuthenticationClient client = new FaceAuthenticationClient(getContext(),
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, opId,
- restricted, opPackageName, 0 /* cookie */, false /* requireConfirmation */,
- getSensorId(), isStrongBiometric(), statsClient, mTaskStackListener,
- mLockoutTracker, mUsageStats);
- authenticateInternal(client, opPackageName);
+ mFace10.scheduleAuthenticate(token, operationId, userId, 0 /* cookie */,
+ new ClientMonitorCallbackConverter(receiver), opPackageName, restricted,
+ statsClient);
}
@Override // Binder call
- public void prepareForAuthentication(boolean requireConfirmation, IBinder token, long opId,
- int userId, IBiometricSensorReceiver sensorReceiver,
+ public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
+ long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
String opPackageName, int cookie, int callingUid, int callingPid,
int callingUserId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- updateActiveGroup(userId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
final boolean restricted = true; // BiometricPrompt is always restricted
- final AuthenticationClient client = new FaceAuthenticationClient(getContext(),
- mLazyDaemon, token, new ClientMonitorCallbackConverter(sensorReceiver), userId,
- opId, restricted, opPackageName, cookie, requireConfirmation, getSensorId(),
- isStrongBiometric(), BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
- mTaskStackListener, mLockoutTracker, mUsageStats);
- authenticateInternal(client, opPackageName, callingUid, callingPid,
- callingUserId);
+ mFace10.scheduleAuthenticate(token, operationId, userId, cookie,
+ new ClientMonitorCallbackConverter(sensorReceiver), opPackageName,
+ restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT);
}
@Override // Binder call
public void startPreparedClient(int cookie) {
- checkPermission(MANAGE_BIOMETRIC);
- startCurrentClient(cookie);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFace10.startPreparedClient(cookie);
}
@Override // Binder call
public void cancelAuthentication(final IBinder token, final String opPackageName) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- cancelAuthenticationInternal(token, opPackageName);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFace10.cancelAuthentication(token);
}
@Override // Binder call
public void cancelAuthenticationFromService(final IBinder token, final String opPackageName,
int callingUid, int callingPid, int callingUserId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- // Cancellation is from system server in this case.
- cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid,
- callingUserId, false /* fromClient */);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFace10.cancelAuthentication(token);
}
@Override // Binder call
public void remove(final IBinder token, final int faceId, final int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
- checkPermission(MANAGE_BIOMETRIC);
- updateActiveGroup(userId);
-
- if (token == null) {
- Slog.w(TAG, "remove(): token is null");
- return;
- }
-
- final RemovalClient client = new FaceRemovalClient(getContext(), mLazyDaemon, token,
- new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
- getBiometricUtils(), getSensorId(), mAuthenticatorIds);
- removeInternal(client);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFace10.scheduleRemove(token, faceId, userId, receiver, opPackageName);
}
@Override
public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
final String opPackageName) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- mHandler.post(() -> {
- mLockoutResetTracker.addCallback(callback, opPackageName);
- });
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mLockoutResetDispatcher.addCallback(callback, opPackageName);
}
@Override // Binder call
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
return;
}
@@ -265,33 +165,21 @@
final long ident = Binder.clearCallingIdentity();
try {
if (args.length > 1 && "--hal".equals(args[0])) {
- dumpHal(fd, Arrays.copyOfRange(args, 1, args.length, args.getClass()));
+ mFace10.dumpHal(fd, Arrays.copyOfRange(args, 1, args.length, args.getClass()));
} else {
- dumpInternal(pw);
+ mFace10.dump(pw);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
- /**
- * The following methods don't use any common code from BiometricService
- */
-
- // TODO: refactor out common code here
@Override // Binder call
public boolean isHardwareDetected(String opPackageName) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
- Binder.getCallingUid(), Binder.getCallingPid(),
- UserHandle.getCallingUserId())) {
- return false;
- }
-
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
final long token = Binder.clearCallingIdentity();
try {
- IBiometricsFace daemon = getFaceDaemon();
- return daemon != null;
+ return mFace10.isHardwareDetected();
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -299,540 +187,66 @@
@Override // Binder call
public List<Face> getEnrolledFaces(int userId, String opPackageName) {
- checkPermission(MANAGE_BIOMETRIC);
- if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
- Binder.getCallingUid(), Binder.getCallingPid(),
- UserHandle.getCallingUserId())) {
- return null;
- }
-
- return FaceService.this.getEnrolledTemplates(userId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ return mFace10.getEnrolledFaces(userId);
}
@Override // Binder call
public boolean hasEnrolledFaces(int userId, String opPackageName) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
- Binder.getCallingUid(), Binder.getCallingPid(),
- UserHandle.getCallingUserId())) {
- return false;
- }
-
- return FaceService.this.hasEnrolledBiometrics(userId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ return !mFace10.getEnrolledFaces(userId).isEmpty();
}
@Override
public @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- return mLockoutTracker.getLockoutModeForUser(userId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ return mFace10.getLockoutModeForUser(userId);
}
@Override // Binder call
- public long getAuthenticatorId(int callingUserId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- return FaceService.this.getAuthenticatorId(callingUserId);
+ public long getAuthenticatorId(int userId) {
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ return mFace10.getAuthenticatorId(userId);
}
@Override // Binder call
public void resetLockout(int userId, byte[] hardwareAuthToken) {
- checkPermission(MANAGE_BIOMETRIC);
- mHandler.post(() -> {
- if (!FaceService.this.hasEnrolledBiometrics(userId)) {
- Slog.w(TAG, "Ignoring lockout reset, no templates enrolled");
- return;
- }
-
- Slog.d(TAG, "Resetting lockout for user: " + userId);
-
- updateActiveGroup(userId);
- final FaceResetLockoutClient client = new FaceResetLockoutClient(getContext(),
- mLazyDaemon, userId, getContext().getOpPackageName(), getSensorId(),
- hardwareAuthToken);
- startClient(client, true /* initiatedByClient */);
- });
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFace10.scheduleResetLockout(userId, hardwareAuthToken);
}
@Override
public void setFeature(final IBinder token, int userId, int feature, boolean enabled,
final byte[] hardwareAuthToken, IFaceServiceReceiver receiver,
final String opPackageName) {
- checkPermission(MANAGE_BIOMETRIC);
-
- mHandler.post(() -> {
- if (DEBUG) {
- Slog.d(TAG, "setFeature for user(" + userId + ")");
- }
- updateActiveGroup(userId);
- if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) {
- Slog.e(TAG, "No enrolled biometrics while setting feature: " + feature);
- return;
- }
-
- final int faceId = getFirstTemplateForUser(mCurrentUserId);
-
- final FaceSetFeatureClient client = new FaceSetFeatureClient(getContext(),
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- opPackageName, getSensorId(), feature, enabled, hardwareAuthToken, faceId);
- startClient(client, true /* initiatedByClient */);
- });
-
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFace10.scheduleSetFeature(token, userId, feature, enabled, hardwareAuthToken, receiver,
+ opPackageName);
}
@Override
public void getFeature(final IBinder token, int userId, int feature,
IFaceServiceReceiver receiver, final String opPackageName) {
- checkPermission(MANAGE_BIOMETRIC);
-
- mHandler.post(() -> {
- if (DEBUG) {
- Slog.d(TAG, "getFeature for user(" + userId + ")");
- }
- updateActiveGroup(userId);
- // This should ideally return tri-state, but the user isn't shown settings unless
- // they are enrolled so it's fine for now.
- if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) {
- Slog.e(TAG, "No enrolled biometrics while getting feature: " + feature);
- return;
- }
-
- // TODO: Support multiple faces
- final int faceId = getFirstTemplateForUser(mCurrentUserId);
-
- final FaceGetFeatureClient client = new FaceGetFeatureClient(getContext(),
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- opPackageName, getSensorId(), feature, faceId);
- startClient(client, true /* initiatedByClient */);
- });
-
- }
-
- // TODO: Support multiple faces
- private int getFirstTemplateForUser(int user) {
- final List<Face> faces = FaceService.this.getEnrolledTemplates(user);
- if (!faces.isEmpty()) {
- return faces.get(0).getBiometricId();
- }
- return 0;
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+ mFace10.scheduleGetFeature(token, userId, feature, receiver, opPackageName);
}
@Override // Binder call
public void initializeConfiguration(int sensorId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- initializeConfigurationInternal(sensorId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFace10 = new Face10(getContext(), sensorId, mLockoutResetDispatcher);
}
}
- private final LockoutHalImpl mLockoutTracker;
-
- @GuardedBy("this")
- private IBiometricsFace mDaemon;
- private UsageStats mUsageStats;
- private RevokeChallengeClient mPendingRevokeChallenge;
-
- private NotificationManager mNotificationManager;
-
- /**
- * Receives callbacks from the HAL.
- */
- private IBiometricsFaceClientCallback mDaemonCallback =
- new IBiometricsFaceClientCallback.Stub() {
- @Override
- public void onEnrollResult(final long deviceId, int faceId, int userId,
- int remaining) {
- mHandler.post(() -> {
- final Face face = new Face(getBiometricUtils()
- .getUniqueName(getContext(), userId), faceId, deviceId);
- FaceService.super.handleEnrollResult(face, remaining);
-
- // Enrollment changes the authenticatorId, so update it here.
- IBiometricsFace daemon = getFaceDaemon();
- if (remaining == 0 && daemon != null) {
- try {
- mAuthenticatorIds.put(userId,
- hasEnrolledBiometrics(userId) ? daemon.getAuthenticatorId().value
- : 0L);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to get authenticatorId", e);
- }
- }
- });
- }
-
- @Override
- public void onAcquired(final long deviceId, final int userId, final int acquiredInfo,
- final int vendorCode) {
- mHandler.post(() -> {
- FaceService.super.handleAcquired(acquiredInfo, vendorCode);
- });
- }
-
- @Override
- public void onAuthenticated(final long deviceId, final int faceId, final int userId,
- ArrayList<Byte> token) {
- mHandler.post(() -> {
- Face face = new Face("", faceId, deviceId);
- FaceService.super.handleAuthenticated(face, token);
- });
- }
-
- @Override
- public void onError(final long deviceId, final int userId, final int error,
- final int vendorCode) {
- mHandler.post(() -> {
- FaceService.super.handleError(error, vendorCode);
-
- // TODO: this chunk of code should be common to all biometric services
- if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
- // If we get HW_UNAVAILABLE, try to connect again later...
- Slog.w(TAG, "Got ERROR_HW_UNAVAILABLE; try reconnecting next client.");
- synchronized (this) {
- mDaemon = null;
- mCurrentUserId = UserHandle.USER_NULL;
- }
- }
- });
- }
-
- @Override
- public void onRemoved(final long deviceId, ArrayList<Integer> faceIds, final int userId) {
- mHandler.post(() -> {
- if (!faceIds.isEmpty()) {
- for (int i = 0; i < faceIds.size(); i++) {
- final Face face = new Face("", faceIds.get(i), deviceId);
- // Convert to old behavior
- FaceService.super.handleRemoved(face, faceIds.size() - i - 1);
- }
- } else {
- 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);
- });
- }
-
- @Override
- public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId)
- throws RemoteException {
- mHandler.post(() -> {
- if (!faceIds.isEmpty()) {
- for (int i = 0; i < faceIds.size(); i++) {
- final Face face = new Face("", faceIds.get(i), deviceId);
- // Convert to old old behavior
- FaceService.super.handleEnumerate(face, faceIds.size() - i - 1);
- }
- } else {
- // For face, the HIDL contract is to receive an empty list when there are no
- // templates enrolled. Send a null identifier since we don't consume them
- // anywhere, and send remaining == 0 to plumb this with existing common code.
- FaceService.super.handleEnumerate(null /* identifier */, 0);
- }
- });
- }
-
- @Override
- public void onLockoutChanged(long duration) {
- Slog.d(TAG, "onLockoutChanged: " + duration);
-
- final @LockoutTracker.LockoutMode int lockoutMode;
- if (duration == 0) {
- lockoutMode = LockoutTracker.LOCKOUT_NONE;
- } else if (duration == -1 || duration == Long.MAX_VALUE) {
- lockoutMode = LockoutTracker.LOCKOUT_PERMANENT;
- } else {
- lockoutMode = LockoutTracker.LOCKOUT_TIMED;
- }
- mLockoutTracker.setCurrentUserLockoutMode(lockoutMode);
-
- mHandler.post(() -> {
- if (duration == 0) {
- mLockoutResetTracker.notifyLockoutResetCallbacks();
- }
- });
- }
- };
-
public FaceService(Context context) {
super(context);
- mLazyDaemon = FaceService.this::getFaceDaemon;
- mLockoutResetTracker = new LockoutResetTracker(context);
- mLockoutTracker = new LockoutHalImpl();
- mUsageStats = new UsageStats(context);
- mNotificationManager = getContext().getSystemService(NotificationManager.class);
- }
-
- @Override
- protected void removeClient(ClientMonitor client) {
- super.removeClient(client);
- if (mPendingRevokeChallenge != null) {
- revokeChallengeInternal(mPendingRevokeChallenge);
- mPendingRevokeChallenge = null;
- }
+ mLockoutResetDispatcher = new LockoutResetDispatcher(context);
}
@Override
public void onStart() {
- super.onStart();
publishBinderService(Context.FACE_SERVICE, new FaceServiceWrapper());
- // Get the face daemon on FaceService's on thread so SystemServerInitThreadPool isn't
- // blocked
- SystemServerInitThreadPool.submit(() -> mHandler.post(this::getFaceDaemon),
- TAG + ".onStart");
- }
-
- @Override
- public String getTag() {
- return TAG;
- }
-
- @Override
- protected IBiometricsFace getDaemon() {
- return getFaceDaemon();
- }
-
- @Override
- protected BiometricUtils getBiometricUtils() {
- return FaceUtils.getInstance();
- }
-
- @Override
- protected boolean hasReachedEnrollmentLimit(int userId) {
- final int limit = getContext().getResources().getInteger(
- com.android.internal.R.integer.config_faceMaxTemplatesPerUser);
- final int enrolled = FaceService.this.getEnrolledTemplates(userId).size();
- if (enrolled >= limit) {
- Slog.w(TAG, "Too many faces registered, user: " + userId);
- return true;
- }
- return false;
- }
-
- @Override
- public void serviceDied(long cookie) {
- super.serviceDied(cookie);
- mDaemon = null;
-
- mCurrentUserId = UserHandle.USER_NULL; // Force updateActiveGroup() to re-evaluate
- }
-
- @Override
- protected void updateActiveGroup(int userId) {
- IBiometricsFace daemon = getFaceDaemon();
-
- if (daemon != null) {
- try {
- if (userId != mCurrentUserId) {
- final File baseDir = Environment.getDataVendorDeDirectory(userId);
- final File faceDir = new File(baseDir, FACE_DATA_DIR);
- if (!faceDir.exists()) {
- if (!faceDir.mkdir()) {
- Slog.v(TAG, "Cannot make directory: " + faceDir.getAbsolutePath());
- return;
- }
- // Calling mkdir() from this process will create a directory with our
- // permissions (inherited from the containing dir). This command fixes
- // the label.
- if (!SELinux.restorecon(faceDir)) {
- Slog.w(TAG, "Restorecons failed. Directory will have wrong label.");
- return;
- }
- }
-
- daemon.setActiveUser(userId, faceDir.getAbsolutePath());
- mCurrentUserId = userId;
- mAuthenticatorIds.put(userId,
- hasEnrolledBiometrics(userId) ? daemon.getAuthenticatorId().value : 0L);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to setActiveUser():", e);
- }
- }
- }
-
- @Override
- protected void handleUserSwitching(int userId) {
- super.handleUserSwitching(userId);
- // Will be updated when we get the callback from HAL
- mLockoutTracker.setCurrentUserLockoutMode(LockoutTracker.LOCKOUT_NONE);
- }
-
- @Override
- protected boolean hasEnrolledBiometrics(int userId) {
- if (userId != UserHandle.getCallingUserId()) {
- checkPermission(INTERACT_ACROSS_USERS);
- }
- return getBiometricUtils().getBiometricsForUser(getContext(), userId).size() > 0;
- }
-
- @Override
- protected String getManageBiometricPermission() {
- return MANAGE_BIOMETRIC;
- }
-
- @Override
- protected void checkUseBiometricPermission() {
- // noop for Face. The permission checks are all done on the incoming binder call.
- }
-
- @Override
- protected boolean checkAppOps(int uid, String opPackageName) {
- return mAppOps.noteOp(AppOpsManager.OP_USE_BIOMETRIC, uid, opPackageName)
- == AppOpsManager.MODE_ALLOWED;
- }
-
- @Override
- protected List<Face> getEnrolledTemplates(int userId) {
- return FaceUtils.getInstance().getBiometricsForUser(getContext(), userId);
- }
-
- @Override
- protected void notifyClientActiveCallbacks(boolean isActive) {
- // noop for Face.
- }
-
- @Override
- protected int statsModality() {
- return BiometricsProtoEnums.MODALITY_FACE;
- }
-
- @Override
- protected @LockoutTracker.LockoutMode int getLockoutMode(int userId) {
- return mLockoutTracker.getLockoutModeForUser(userId);
- }
-
- @Override
- protected void doTemplateCleanupForUser(int userId) {
- final List<Face> enrolledList = getEnrolledTemplates(userId);
- final FaceInternalCleanupClient client = new FaceInternalCleanupClient(getContext(),
- mLazyDaemon, userId, getContext().getOpPackageName(), getSensorId(), enrolledList,
- getBiometricUtils(), mAuthenticatorIds);
- cleanupInternal(client);
- }
-
-
- /** Gets the face daemon */
- private synchronized IBiometricsFace getFaceDaemon() {
- if (mDaemon == null) {
- Slog.v(TAG, "mDaemon was null, reconnect to face");
- try {
- mDaemon = IBiometricsFace.getService();
- } catch (java.util.NoSuchElementException e) {
- // Service doesn't exist or cannot be opened. Logged below.
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to get biometric interface", e);
- }
- if (mDaemon == null) {
- Slog.w(TAG, "face HIDL not available");
- return null;
- }
-
- mDaemon.asBinder().linkToDeath(this, 0);
-
- long halId = 0;
- try {
- halId = mDaemon.setCallback(mDaemonCallback).value;
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to open face HAL", e);
- mDaemon = null; // try again later!
- }
-
- if (DEBUG) Slog.v(TAG, "Face HAL id: " + halId);
- if (halId != 0) {
- loadAuthenticatorIds();
- updateActiveGroup(ActivityManager.getCurrentUser());
- doTemplateCleanupForUser(ActivityManager.getCurrentUser());
- } else {
- Slog.w(TAG, "Failed to open Face HAL!");
- MetricsLogger.count(getContext(), "faced_openhal_error", 1);
- mDaemon = null;
- }
- }
- return mDaemon;
}
private native NativeHandle convertSurfaceToNativeHandle(Surface surface);
-
- private void dumpInternal(PrintWriter pw) {
- PerformanceTracker performanceTracker =
- PerformanceTracker.getInstanceForSensorId(getSensorId());
-
- JSONObject dump = new JSONObject();
- try {
- dump.put("service", "Face Manager");
-
- JSONArray sets = new JSONArray();
- for (UserInfo user : UserManager.get(getContext()).getUsers()) {
- final int userId = user.getUserHandle().getIdentifier();
- final int N = getBiometricUtils().getBiometricsForUser(getContext(), userId).size();
- JSONObject set = new JSONObject();
- set.put("id", userId);
- set.put("count", N);
- set.put("accept", performanceTracker.getAcceptForUser(userId));
- set.put("reject", performanceTracker.getRejectForUser(userId));
- set.put("acquire", performanceTracker.getAcquireForUser(userId));
- set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
- set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
- // cryptoStats measures statistics about secure face transactions
- // (e.g. to unlock password storage, make secure purchases, etc.)
- set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
- set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
- set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
- sets.put(set);
- }
-
- dump.put("prints", sets);
- } catch (JSONException e) {
- Slog.e(TAG, "dump formatting failure", e);
- }
- pw.println(dump);
- pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
-
- mUsageStats.print(pw);
- }
-
- private void dumpHal(FileDescriptor fd, String[] args) {
- // WARNING: CDD restricts image data from leaving TEE unencrypted on
- // production devices:
- // [C-1-10] MUST not allow unencrypted access to identifiable biometric
- // data or any data derived from it (such as embeddings) to the
- // Application Processor outside the context of the TEE.
- // As such, this API should only be enabled for testing purposes on
- // engineering and userdebug builds. All modules in the software stack
- // MUST enforce final build products do NOT have this functionality.
- // Additionally, the following check MUST NOT be removed.
- if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
- return;
- }
-
- // Additionally, this flag allows turning off face for a device
- // (either permanently through the build or on an individual device).
- if (SystemProperties.getBoolean("ro.face.disable_debug_data", false)
- || SystemProperties.getBoolean("persist.face.disable_debug_data", false)) {
- return;
- }
-
- // The debug method takes two file descriptors. The first is for text
- // output, which we will drop. The second is for binary data, which
- // will be the protobuf data.
- final IBiometricsFace daemon = getFaceDaemon();
- if (daemon != null) {
- FileOutputStream devnull = null;
- try {
- devnull = new FileOutputStream("/dev/null");
- final NativeHandle handle = new NativeHandle(
- new FileDescriptor[] { devnull.getFD(), fd },
- new int[0], false);
- daemon.debug(handle, new ArrayList<String>(Arrays.asList(args)));
- } catch (IOException | RemoteException ex) {
- Slog.d(TAG, "error while reading face debugging data", ex);
- } finally {
- if (devnull != null) {
- try {
- devnull.close();
- } catch (IOException ex) {
- }
- }
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java
new file mode 100644
index 0000000..bcf304e
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceUpdateActiveUserClient.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 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.biometrics.sensors.face;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.ClientMonitor;
+
+import java.io.File;
+import java.util.Map;
+
+public class FaceUpdateActiveUserClient extends ClientMonitor<IBiometricsFace> {
+ private static final String TAG = "FaceUpdateActiveUserClient";
+ private static final String FACE_DATA_DIR = "facedata";
+
+ private final int mCurrentUserId;
+ private final boolean mHasEnrolledBiometrics;
+ @NonNull private final Map<Integer, Long> mAuthenticatorIds;
+
+ FaceUpdateActiveUserClient(@NonNull Context context,
+ @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
+ int sensorId, int currentUserId, boolean hasEnrolledBIometrics,
+ @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
+ 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ mCurrentUserId = currentUserId;
+ mHasEnrolledBiometrics = hasEnrolledBIometrics;
+ mAuthenticatorIds = authenticatorIds;
+ }
+
+ @Override
+ public void start(@NonNull FinishCallback finishCallback) {
+ super.start(finishCallback);
+
+ if (mCurrentUserId == getTargetUserId()) {
+ Slog.d(TAG, "Already user: " + mCurrentUserId + ", refreshing authenticatorId");
+ try {
+ mAuthenticatorIds.put(getTargetUserId(), mHasEnrolledBiometrics
+ ? getFreshDaemon().getAuthenticatorId().value : 0L);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to refresh authenticatorId", e);
+ }
+ finishCallback.onClientFinished(this, true /* success */);
+ return;
+ }
+
+ startHalOperation();
+ }
+
+ @Override
+ public void unableToStart() {
+ // Nothing to do here
+ }
+
+ @Override
+ protected void startHalOperation() {
+ final File storePath = new File(Environment.getDataVendorDeDirectory(getTargetUserId()),
+ FACE_DATA_DIR);
+ if (!storePath.exists()) {
+ Slog.e(TAG, "vold has not created the directory?");
+ mFinishCallback.onClientFinished(this, false /* success */);
+ return;
+ }
+
+ try {
+ getFreshDaemon().setActiveUser(getTargetUserId(), storePath.getAbsolutePath());
+ mFinishCallback.onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to setActiveUser: " + e);
+ mFinishCallback.onClientFinished(this, false /* success */);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
new file mode 100644
index 0000000..9f94c88
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
@@ -0,0 +1,656 @@
+/*
+ * Copyright (C) 2020 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.biometrics.sensors.fingerprint;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
+import android.app.SynchronousUserSwitchObserver;
+import android.app.TaskStackListener;
+import android.app.UserSwitchObserver;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IHwBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface;
+
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto;
+import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto;
+import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
+import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.ClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.EnumerateConsumer;
+import com.android.server.biometrics.sensors.Interruptable;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.PerformanceTracker;
+import com.android.server.biometrics.sensors.RemovalConsumer;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Supports a single instance of the {@link android.hardware.biometrics.fingerprint.V2_1} or
+ * its extended minor versions.
+ */
+class Fingerprint21 implements IHwBinder.DeathRecipient {
+
+ private static final String TAG = "Fingerprint21";
+ private static final int ENROLL_TIMEOUT_SEC = 60;
+
+ private final Context mContext;
+ private final IActivityTaskManager mActivityTaskManager;
+ private final SensorProperties mSensorProperties;
+ private final BiometricScheduler mScheduler;
+ private final Handler mHandler;
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
+ private final LockoutFrameworkImpl mLockoutTracker;
+ private final BiometricTaskStackListener mTaskStackListener;
+ private final ClientMonitor.LazyDaemon<IBiometricsFingerprint> mLazyDaemon;
+ private final Map<Integer, Long> mAuthenticatorIds;
+
+ @Nullable private IBiometricsFingerprint mDaemon;
+ @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+
+ /**
+ * Static properties that never change for a given sensor.
+ */
+ private static final class SensorProperties {
+ final int sensorId;
+ final boolean isUdfps;
+
+ SensorProperties(int sensorId, boolean isUdfps) {
+ this.sensorId = sensorId;
+ this.isUdfps = isUdfps;
+ }
+ }
+
+ private final class BiometricTaskStackListener extends TaskStackListener {
+ @Override
+ public void onTaskStackChanged() {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof AuthenticationClient)) {
+ Slog.e(TAG, "Task stack changed for client: " + client);
+ return;
+ }
+ if (Utils.isKeyguard(mContext, client.getOwnerString())) {
+ return; // Keyguard is always allowed
+ }
+
+ try {
+ final List<ActivityManager.RunningTaskInfo> runningTasks =
+ mActivityTaskManager.getTasks(1);
+ if (!runningTasks.isEmpty()) {
+ final String topPackage = runningTasks.get(0).topActivity.getPackageName();
+ if (!topPackage.contentEquals(client.getOwnerString())
+ && !client.isAlreadyDone()) {
+ Slog.e(TAG, "Stopping background authentication, top: "
+ + topPackage + " currentClient: " + client);
+ mScheduler.cancelAuthentication(client.getToken());
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to get running tasks", e);
+ }
+ });
+ }
+ }
+
+ private final LockoutFrameworkImpl.LockoutResetCallback mLockoutResetCallback =
+ new LockoutFrameworkImpl.LockoutResetCallback() {
+ @Override
+ public void onLockoutReset(int userId) {
+ mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorProperties.sensorId);
+ }
+ };
+
+ private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(newUserId);
+ }
+ };
+
+ private final IBiometricsFingerprintClientCallback mDaemonCallback =
+ new IBiometricsFingerprintClientCallback.Stub() {
+ @Override
+ public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
+ mHandler.post(() -> {
+ final CharSequence name = FingerprintUtils.getInstance()
+ .getUniqueName(mContext, mCurrentUserId);
+ final Fingerprint fingerprint = new Fingerprint(name, groupId, fingerId, deviceId);
+
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintEnrollClient)) {
+ Slog.e(TAG, "onEnrollResult for non-enroll client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
+ enrollClient.onEnrollResult(fingerprint, remaining);
+ });
+ }
+
+ @Override
+ public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) {
+ onAcquired_2_2(deviceId, acquiredInfo, vendorCode);
+ }
+
+ @Override
+ public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof AcquisitionClient)) {
+ Slog.e(TAG, "onAcquired for non-acquisition client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
+ acquisitionClient.onAcquired(acquiredInfo, vendorCode);
+ });
+ }
+
+ @Override
+ public void onAuthenticated(long deviceId, int fingerId, int groupId,
+ ArrayList<Byte> token) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintAuthenticationClient)) {
+ Slog.e(TAG, "onAuthenticated for non-authentication client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FingerprintAuthenticationClient authenticationClient =
+ (FingerprintAuthenticationClient) client;
+ final boolean authenticated = fingerId != 0;
+ final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
+ authenticationClient.onAuthenticated(fp, authenticated, token);
+ });
+ }
+
+ @Override
+ public void onError(long deviceId, int error, int vendorCode) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ Slog.d(TAG, "handleError"
+ + ", client: " + (client != null ? client.getOwnerString() : null)
+ + ", error: " + error
+ + ", vendorCode: " + vendorCode);
+ if (!(client instanceof Interruptable)) {
+ Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(client));
+ return;
+ }
+
+ final Interruptable interruptable = (Interruptable) client;
+ interruptable.onError(error, vendorCode);
+
+ if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
+ Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
+ mDaemon = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }
+ });
+ }
+
+ @Override
+ public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
+ mHandler.post(() -> {
+ Slog.d(TAG, "Removed, fingerId: " + fingerId + ", remaining: " + remaining);
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof RemovalConsumer)) {
+ Slog.e(TAG, "onRemoved for non-removal consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
+ final RemovalConsumer removalConsumer = (RemovalConsumer) client;
+ removalConsumer.onRemoved(fp, remaining);
+ });
+ }
+
+ @Override
+ public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof EnumerateConsumer)) {
+ Slog.e(TAG, "onEnumerate for non-enumerate consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
+ final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
+ enumerateConsumer.onEnumerationResult(fp, remaining);
+ });
+ }
+ };
+
+ Fingerprint21(@NonNull Context context, int sensorId,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ mContext = context;
+ mActivityTaskManager = ActivityTaskManager.getService();
+ mHandler = new Handler(Looper.getMainLooper());
+ mTaskStackListener = new BiometricTaskStackListener();
+ mAuthenticatorIds = Collections.synchronizedMap(new HashMap<>());
+ mLazyDaemon = Fingerprint21.this::getDaemon;
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+ mLockoutTracker = new LockoutFrameworkImpl(context, mLockoutResetCallback);
+ mScheduler = new BiometricScheduler(TAG, gestureAvailabilityDispatcher);
+
+ try {
+ ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to register user switch observer");
+ }
+
+ final IBiometricsFingerprint daemon = getDaemon();
+ boolean isUdfps = false;
+ android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint extension =
+ android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint.castFrom(
+ daemon);
+ if (extension != null) {
+ try {
+ isUdfps = extension.isUdfps(sensorId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception while quering udfps", e);
+ isUdfps = false;
+ }
+ }
+ mSensorProperties = new SensorProperties(sensorId, isUdfps);
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ Slog.e(TAG, "HAL died");
+ mHandler.post(() -> {
+ PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId)
+ .incrementHALDeathCount();
+ mDaemon = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (client instanceof Interruptable) {
+ Slog.e(TAG, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
+ final Interruptable interruptable = (Interruptable) client;
+ interruptable.onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+
+ mScheduler.recordCrashState();
+
+ FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
+ BiometricsProtoEnums.MODALITY_FINGERPRINT,
+ BiometricsProtoEnums.ISSUE_HAL_DEATH);
+ }
+ });
+ }
+
+ private synchronized IBiometricsFingerprint getDaemon() {
+ if (mDaemon != null) {
+ return mDaemon;
+ }
+
+ Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
+ + mScheduler.getCurrentClient());
+ try {
+ mDaemon = IBiometricsFingerprint.getService();
+ } catch (java.util.NoSuchElementException e) {
+ // Service doesn't exist or cannot be opened.
+ Slog.w(TAG, "NoSuchElementException", e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get fingerprint HAL", e);
+ }
+
+ if (mDaemon == null) {
+ Slog.w(TAG, "Fingerprint HAL not available");
+ return null;
+ }
+
+ mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
+
+ // HAL ID for these HIDL versions are only used to determine if callbacks have been
+ // successfully set.
+ long halId = 0;
+ try {
+ halId = mDaemon.setNotify(mDaemonCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set callback for fingerprint HAL", e);
+ mDaemon = null;
+ }
+
+ Slog.d(TAG, "Fingerprint HAL ready, HAL ID: " + halId);
+ if (halId != 0) {
+ scheduleLoadAuthenticatorIds();
+ scheduleInternalCleanup(ActivityManager.getCurrentUser());
+ } else {
+ Slog.e(TAG, "Unable to set callback");
+ mDaemon = null;
+ }
+
+ return mDaemon;
+ }
+
+ @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) {
+ return mLockoutTracker.getLockoutModeForUser(userId);
+ }
+
+ private void scheduleLoadAuthenticatorIds() {
+ // Note that this can be performed on the scheduler (as opposed to being done immediately
+ // when the HAL is (re)loaded, since
+ // 1) If this is truly the first time it's being performed (e.g. system has just started),
+ // this will be run very early and way before any applications need to generate keys.
+ // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
+ // just been reloaded), the framework already has a cache of the authenticatorIds. This
+ // is safe because authenticatorIds only change when A) new template has been enrolled,
+ // or B) all templates are removed.
+ mHandler.post(() -> {
+ for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
+ final int targetUserId = user.id;
+ if (!mAuthenticatorIds.containsKey(targetUserId)) {
+ scheduleUpdateActiveUserWithoutHandler(targetUserId);
+ }
+ }
+ });
+ }
+
+ /**
+ * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the
+ * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
+ * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
+ * this operation on the same lambda/runnable as those operations so that the ordering is
+ * correct.
+ */
+ private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
+ final boolean hasEnrolled = !getEnrolledFingerprints(targetUserId).isEmpty();
+ final FingerprintUpdateActiveUserClient client =
+ new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
+ mContext.getOpPackageName(), mSensorProperties.sensorId, mCurrentUserId,
+ hasEnrolled, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, (clientMonitor, success) -> {
+ if (success) {
+ mCurrentUserId = targetUserId;
+ }
+ });
+ }
+
+ void scheduleResetLockout(int userId, byte[] hardwareAuthToken) {
+ // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler
+ // thread.
+ mHandler.post(() -> {
+ mLockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId);
+ });
+ }
+
+ void scheduleGenerateChallenge(@NonNull IBinder token,
+ @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ final FingerprintGenerateChallengeClient client =
+ new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
+ new ClientMonitorCallbackConverter(receiver), opPackageName,
+ mSensorProperties.sensorId);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
+ mContext, mLazyDaemon, token, opPackageName, mSensorProperties.sensorId);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void scheduleEnroll(@NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId,
+ @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
+ @Nullable Surface surface) {
+ mHandler.post(() -> {
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
+ hardwareAuthToken, opPackageName, FingerprintUtils.getInstance(),
+ ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController);
+ mScheduler.scheduleClientMonitor(client, ((clientMonitor, success) -> {
+ if (success) {
+ // Update authenticatorIds
+ scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId());
+ }
+ }));
+ });
+ }
+
+ void cancelEnrollment(@NonNull IBinder token) {
+ mHandler.post(() -> {
+ mScheduler.cancelEnrollment(token);
+ });
+ }
+
+ void scheduleAuthenticate(@NonNull IBinder token, long operationId, int userId, int cookie,
+ @NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
+ @Nullable Surface surface, boolean restricted, int statsClient) {
+ mHandler.post(() -> {
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
+ final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
+ mContext, mLazyDaemon, token, listener, userId, operationId, restricted,
+ opPackageName, cookie, false /* requireConfirmation */,
+ mSensorProperties.sensorId, isStrongBiometric, surface, statsClient,
+ mTaskStackListener, mLockoutTracker, mUdfpsOverlayController);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ void startPreparedClient(int cookie) {
+ mHandler.post(() -> {
+ mScheduler.startPreparedClient(cookie);
+ });
+ }
+
+ void cancelAuthentication(@NonNull IBinder token) {
+ mHandler.post(() -> {
+ mScheduler.cancelAuthentication(token);
+ });
+ }
+
+ void scheduleRemove(@NonNull IBinder token, @NonNull IFingerprintServiceReceiver receiver,
+ int fingerId, int userId, @NonNull String opPackageName) {
+ mHandler.post(() -> {
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
+ userId, opPackageName, FingerprintUtils.getInstance(),
+ mSensorProperties.sensorId, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ private void scheduleInternalCleanup(int userId) {
+ mHandler.post(() -> {
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ final List<Fingerprint> enrolledList = getEnrolledFingerprints(userId);
+ final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
+ mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
+ mSensorProperties.sensorId, enrolledList, FingerprintUtils.getInstance(),
+ mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client);
+ });
+ }
+
+ boolean isHardwareDetected() {
+ final IBiometricsFingerprint daemon = getDaemon();
+ return daemon != null;
+ }
+
+ void rename(int fingerId, int userId, String name) {
+ mHandler.post(() -> {
+ FingerprintUtils.getInstance().renameBiometricForUser(mContext, userId, fingerId, name);
+ });
+ }
+
+ List<Fingerprint> getEnrolledFingerprints(int userId) {
+ return FingerprintUtils.getInstance().getBiometricsForUser(mContext, userId);
+ }
+
+ long getAuthenticatorId(int userId) {
+ return mAuthenticatorIds.get(userId);
+ }
+
+ void onFingerDown(int x, int y, float minor, float major) {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof Udfps)) {
+ Slog.w(TAG, "onFingerDown received during client: " + client);
+ return;
+ }
+ final Udfps udfps = (Udfps) client;
+ udfps.onFingerDown(x, y, minor, major);
+ }
+
+ void onFingerUp() {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof Udfps)) {
+ Slog.w(TAG, "onFingerDown received during client: " + client);
+ return;
+ }
+ final Udfps udfps = (Udfps) client;
+ udfps.onFingerUp();
+ }
+
+ boolean isUdfps() {
+ return mSensorProperties.isUdfps;
+ }
+
+ void setUdfpsOverlayController(IUdfpsOverlayController controller) {
+ mUdfpsOverlayController = controller;
+ }
+
+ void dumpProto(FileDescriptor fd) {
+ PerformanceTracker tracker =
+ PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
+
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ for (UserInfo user : UserManager.get(mContext).getUsers()) {
+ final int userId = user.getUserHandle().getIdentifier();
+
+ final long userToken = proto.start(FingerprintServiceDumpProto.USERS);
+
+ proto.write(FingerprintUserStatsProto.USER_ID, userId);
+ proto.write(FingerprintUserStatsProto.NUM_FINGERPRINTS,
+ FingerprintUtils.getInstance().getBiometricsForUser(mContext, userId).size());
+
+ // Normal fingerprint authentications (e.g. lockscreen)
+ long countsToken = proto.start(FingerprintUserStatsProto.NORMAL);
+ proto.write(PerformanceStatsProto.ACCEPT, tracker.getAcceptForUser(userId));
+ proto.write(PerformanceStatsProto.REJECT, tracker.getRejectForUser(userId));
+ proto.write(PerformanceStatsProto.ACQUIRE, tracker.getAcquireForUser(userId));
+ proto.write(PerformanceStatsProto.LOCKOUT, tracker.getTimedLockoutForUser(userId));
+ proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT,
+ tracker.getPermanentLockoutForUser(userId));
+ proto.end(countsToken);
+
+ // Statistics about secure fingerprint transactions (e.g. to unlock password
+ // storage, make secure purchases, etc.)
+ countsToken = proto.start(FingerprintUserStatsProto.CRYPTO);
+ proto.write(PerformanceStatsProto.ACCEPT, tracker.getAcceptCryptoForUser(userId));
+ proto.write(PerformanceStatsProto.REJECT, tracker.getRejectCryptoForUser(userId));
+ proto.write(PerformanceStatsProto.ACQUIRE, tracker.getAcquireCryptoForUser(userId));
+ proto.write(PerformanceStatsProto.LOCKOUT, 0); // meaningless for crypto
+ proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, 0); // meaningless for crypto
+ proto.end(countsToken);
+
+ proto.end(userToken);
+ }
+ proto.flush();
+ tracker.clear();
+ }
+
+ void dumpInternal(@NonNull PrintWriter pw) {
+ PerformanceTracker performanceTracker =
+ PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
+
+ JSONObject dump = new JSONObject();
+ try {
+ dump.put("service", "Fingerprint Manager");
+
+ JSONArray sets = new JSONArray();
+ for (UserInfo user : UserManager.get(mContext).getUsers()) {
+ final int userId = user.getUserHandle().getIdentifier();
+ final int N = FingerprintUtils.getInstance()
+ .getBiometricsForUser(mContext, userId).size();
+ JSONObject set = new JSONObject();
+ set.put("id", userId);
+ set.put("count", N);
+ set.put("accept", performanceTracker.getAcceptForUser(userId));
+ set.put("reject", performanceTracker.getRejectForUser(userId));
+ set.put("acquire", performanceTracker.getAcquireForUser(userId));
+ set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
+ set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
+ // cryptoStats measures statistics about secure fingerprint transactions
+ // (e.g. to unlock password storage, make secure purchases, etc.)
+ set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
+ set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
+ set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
+ sets.put(set);
+ }
+
+ dump.put("prints", sets);
+ } catch (JSONException e) {
+ Slog.e(TAG, "dump formatting failure", e);
+ }
+ pw.println(dump);
+ pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
+ mScheduler.dump(pw);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java
index b01b856..1999a0f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java
@@ -25,6 +25,7 @@
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -41,11 +42,13 @@
* {@link android.hardware.biometrics.fingerprint.V2_1} and
* {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
*/
-class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFingerprint> {
+class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFingerprint>
+ implements Udfps {
private static final String TAG = "Biometrics/FingerprintAuthClient";
private final LockoutFrameworkImpl mLockoutFrameworkImpl;
+ @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
FingerprintAuthenticationClient(@NonNull Context context,
@NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
@@ -53,12 +56,34 @@
boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
int sensorId, boolean isStrongBiometric, @Nullable Surface surface, int statsClient,
@NonNull TaskStackListener taskStackListener,
- @NonNull LockoutFrameworkImpl lockoutTracker) {
+ @NonNull LockoutFrameworkImpl lockoutTracker,
+ @Nullable IUdfpsOverlayController udfpsOverlayController) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
lockoutTracker);
mLockoutFrameworkImpl = lockoutTracker;
+ mUdfpsOverlayController = udfpsOverlayController;
+ }
+
+ private void showUdfpsOverlay() {
+ if (mUdfpsOverlayController != null) {
+ try {
+ mUdfpsOverlayController.showUdfpsOverlay();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
+ }
+ }
+ }
+
+ private void hideUdfpsOverlay() {
+ if (mUdfpsOverlayController != null) {
+ try {
+ mUdfpsOverlayController.hideUdfpsOverlay();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
+ }
+ }
}
@Override
@@ -73,18 +98,22 @@
if (authenticated) {
resetFailedAttempts(getTargetUserId());
+ hideUdfpsOverlay();
mFinishCallback.onClientFinished(this, true /* success */);
} else {
final @LockoutTracker.LockoutMode int lockoutMode =
mLockoutFrameworkImpl.getLockoutModeForUser(getTargetUserId());
if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
Slog.w(TAG, "Fingerprint locked out, lockoutMode(" + lockoutMode + ")");
- cancel();
final int errorCode = lockoutMode == LockoutTracker.LOCKOUT_TIMED
? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
: BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
- onError(errorCode, 0 /* vendorCode */);
- mFinishCallback.onClientFinished(this, true /* success */);
+ // Send the error, but do not invoke the FinishCallback yet. Since lockout is not
+ // controlled by the HAL, the framework must stop the sensor before finishing the
+ // client.
+ hideUdfpsOverlay();
+ onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
+ cancel();
}
}
}
@@ -101,6 +130,7 @@
@Override
protected void startHalOperation() {
+ showUdfpsOverlay();
try {
// GroupId was never used. In fact, groupId is always the same as userId.
getFreshDaemon().authenticate(mOperationId, getTargetUserId());
@@ -108,12 +138,14 @@
Slog.e(TAG, "Remote exception when requesting auth", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
+ hideUdfpsOverlay();
mFinishCallback.onClientFinished(this, false /* success */);
}
}
@Override
protected void stopHalOperation() {
+ hideUdfpsOverlay();
try {
getFreshDaemon().cancel();
} catch (RemoteException e) {
@@ -123,4 +155,14 @@
mFinishCallback.onClientFinished(this, false /* success */);
}
}
+
+ @Override
+ public void onFingerDown(int x, int y, float minor, float major) {
+ UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major);
+ }
+
+ @Override
+ public void onFingerUp() {
+ UdfpsHelper.onFingerUp(getFreshDaemon());
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
index 1d56882..25e949a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
@@ -17,10 +17,12 @@
package com.android.server.biometrics.sensors.fingerprint;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -34,22 +36,61 @@
* {@link android.hardware.biometrics.fingerprint.V2_1} and
* {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
*/
-public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint> {
+public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint>
+ implements Udfps {
private static final String TAG = "FingerprintEnrollClient";
+ @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
+
FingerprintEnrollClient(@NonNull Context context,
@NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils,
- int timeoutSec, int sensorId) {
+ int timeoutSec, int sensorId,
+ @Nullable IUdfpsOverlayController udfpsOverlayController) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
true /* shouldVibrate */);
+ mUdfpsOverlayController = udfpsOverlayController;
+ }
+
+ private void showUdfpsOverlay() {
+ if (mUdfpsOverlayController != null) {
+ try {
+ mUdfpsOverlayController.showUdfpsOverlay();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
+ }
+ }
+ }
+
+ private void hideUdfpsOverlay() {
+ if (mUdfpsOverlayController != null) {
+ try {
+ mUdfpsOverlayController.hideUdfpsOverlay();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
+ }
+ }
+ }
+
+ @Override
+ protected boolean hasReachedEnrollmentLimit() {
+ final int limit = getContext().getResources().getInteger(
+ com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
+ final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
+ .size();
+ if (enrolled >= limit) {
+ Slog.w(TAG, "Too many faces registered, user: " + getTargetUserId());
+ return true;
+ }
+ return false;
}
@Override
protected void startHalOperation() {
+ showUdfpsOverlay();
try {
// GroupId was never used. In fact, groupId is always the same as userId.
getFreshDaemon().enroll(mHardwareAuthToken, getTargetUserId(), mTimeoutSec);
@@ -57,12 +98,14 @@
Slog.e(TAG, "Remote exception when requesting enroll", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
+ hideUdfpsOverlay();
mFinishCallback.onClientFinished(this, false /* success */);
}
}
@Override
protected void stopHalOperation() {
+ hideUdfpsOverlay();
try {
getFreshDaemon().cancel();
} catch (RemoteException e) {
@@ -72,4 +115,14 @@
mFinishCallback.onClientFinished(this, false /* success */);
}
}
+
+ @Override
+ public void onFingerDown(int x, int y, float minor, float major) {
+ UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major);
+ }
+
+ @Override
+ public void onFingerUp() {
+ UdfpsHelper.onFingerUp(getFreshDaemon());
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalCleanupClient.java
index 1d72c2e..571d2b8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalCleanupClient.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index b05400a..e0cb7ec9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -23,64 +23,39 @@
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.IFingerprintClientActiveCallback;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Binder;
-import android.os.Build;
-import android.os.Environment;
import android.os.IBinder;
import android.os.NativeHandle;
+import android.os.Process;
import android.os.RemoteException;
-import android.os.SELinux;
import android.os.UserHandle;
-import android.os.UserManager;
import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
import android.view.Surface;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.DumpUtils;
-import com.android.server.SystemServerInitThreadPool;
-import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto;
-import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto;
-import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
-import com.android.server.biometrics.sensors.AuthenticationClient;
-import com.android.server.biometrics.sensors.BiometricServiceBase;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.ClientMonitor;
+import com.android.server.SystemService;
+import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.EnrollClient;
-import com.android.server.biometrics.sensors.GenerateChallengeClient;
-import com.android.server.biometrics.sensors.LockoutResetTracker;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.RemovalClient;
-import com.android.server.biometrics.sensors.RevokeChallengeClient;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -88,163 +63,130 @@
* A service to manage multiple clients that want to access the fingerprint HAL API.
* The service is responsible for maintaining a list of clients and dispatching all
* fingerprint-related events.
- *
- * @hide
*/
-public class FingerprintService extends BiometricServiceBase<IBiometricsFingerprint> {
+public class FingerprintService extends SystemService {
protected static final String TAG = "FingerprintService";
- private static final boolean DEBUG = true;
- private static final String FP_DATA_DIR = "fpdata";
- private final LockoutResetTracker mLockoutResetTracker;
- private final ClientMonitor.LazyDaemon<IBiometricsFingerprint> mLazyDaemon;
- private final GestureAvailabilityTracker mGestureAvailabilityTracker;
+ private final AppOpsManager mAppOps;
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
+ private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
+ private Fingerprint21 mFingerprint21;
/**
* Receives the incoming binder calls from FingerprintManager.
*/
private final class FingerprintServiceWrapper extends IFingerprintService.Stub {
- private static final int ENROLL_TIMEOUT_SEC = 60;
-
- /**
- * The following methods contain common code which is shared in biometrics/common.
- */
-
@Override // Binder call
public void generateChallenge(IBinder token, IFingerprintServiceReceiver receiver,
String opPackageName) {
- checkPermission(MANAGE_FINGERPRINT);
-
- final GenerateChallengeClient client = new FingerprintGenerateChallengeClient(
- getContext(), mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver),
- opPackageName, getSensorId());
- generateChallengeInternal(client);
+ Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
+ mFingerprint21.scheduleGenerateChallenge(token, receiver, opPackageName);
}
@Override // Binder call
public void revokeChallenge(IBinder token, String owner) {
- checkPermission(MANAGE_FINGERPRINT);
-
- final RevokeChallengeClient client = new FingerprintRevokeChallengeClient(getContext(),
- mLazyDaemon, token, owner, getSensorId());
- revokeChallengeInternal(client);
+ Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
+ mFingerprint21.scheduleRevokeChallenge(token, owner);
}
@Override // Binder call
- public void enroll(final IBinder token, final byte[] cryptoToken, final int userId,
+ public void enroll(final IBinder token, final byte[] hardwareAuthToken, final int userId,
final IFingerprintServiceReceiver receiver, final String opPackageName,
- final Surface surface) {
- checkPermission(MANAGE_FINGERPRINT);
- updateActiveGroup(userId);
-
- final EnrollClient client = new FingerprintEnrollClient(getContext(), mLazyDaemon,
- token, new ClientMonitorCallbackConverter(receiver), userId, cryptoToken,
- opPackageName, getBiometricUtils(), ENROLL_TIMEOUT_SEC, getSensorId());
-
- enrollInternal(client, userId);
+ Surface surface) {
+ Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
+ mFingerprint21.scheduleEnroll(token, hardwareAuthToken, userId, receiver, opPackageName,
+ surface);
}
@Override // Binder call
public void cancelEnrollment(final IBinder token) {
- checkPermission(MANAGE_FINGERPRINT);
- cancelEnrollmentInternal(token);
+ Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
+ mFingerprint21.cancelEnrollment(token);
}
@Override // Binder call
- public void authenticate(final IBinder token, final long opId, final int userId,
+ public void authenticate(final IBinder token, final long operationId, final int userId,
final IFingerprintServiceReceiver receiver, final String opPackageName,
- final Surface surface) {
- updateActiveGroup(userId);
+ Surface surface) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int callingUserId = UserHandle.getCallingUserId();
- final boolean isStrongBiometric;
- final long ident = Binder.clearCallingIdentity();
- try {
- isStrongBiometric = isStrongBiometric();
- } finally {
- Binder.restoreCallingIdentity(ident);
+ if (!canUseFingerprint(opPackageName, true /* requireForeground */, callingUid,
+ callingPid, callingUserId)) {
+ Slog.w(TAG, "Authenticate rejecting package: " + opPackageName);
+ return;
}
- final boolean restricted = isRestricted();
- final int statsClient = isKeyguard(opPackageName) ? BiometricsProtoEnums.CLIENT_KEYGUARD
+ final boolean restricted = getContext().checkCallingPermission(MANAGE_FINGERPRINT)
+ != PackageManager.PERMISSION_GRANTED;
+ final int statsClient = Utils.isKeyguard(getContext(), opPackageName)
+ ? BiometricsProtoEnums.CLIENT_KEYGUARD
: BiometricsProtoEnums.CLIENT_FINGERPRINT_MANAGER;
- final AuthenticationClient client = new FingerprintAuthenticationClient(getContext(),
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, opId,
- restricted, opPackageName, 0 /* cookie */, false /* requireConfirmation */,
- getSensorId(), isStrongBiometric, surface, statsClient, mTaskStackListener,
- mLockoutTracker);
- authenticateInternal(client, opPackageName);
+ mFingerprint21.scheduleAuthenticate(token, operationId, userId, 0 /* cookie */,
+ new ClientMonitorCallbackConverter(receiver), opPackageName, surface,
+ restricted, statsClient);
}
@Override // Binder call
- public void prepareForAuthentication(IBinder token, long opId, int userId,
+ public void prepareForAuthentication(IBinder token, long operationId, int userId,
IBiometricSensorReceiver sensorReceiver, String opPackageName,
int cookie, int callingUid, int callingPid, int callingUserId,
Surface surface) {
- checkPermission(MANAGE_BIOMETRIC);
- updateActiveGroup(userId);
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
final boolean restricted = true; // BiometricPrompt is always restricted
- final AuthenticationClient client = new FingerprintAuthenticationClient(getContext(),
- mLazyDaemon, token, new ClientMonitorCallbackConverter(sensorReceiver), userId,
- opId, restricted, opPackageName, cookie, false /* requireConfirmation */,
- getSensorId(), isStrongBiometric(), surface,
- BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, mTaskStackListener,
- mLockoutTracker);
- authenticateInternal(client, opPackageName, callingUid, callingPid,
- callingUserId);
+ mFingerprint21.scheduleAuthenticate(token, operationId, userId, cookie,
+ new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, surface,
+ restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT);
}
@Override // Binder call
public void startPreparedClient(int cookie) {
- checkPermission(MANAGE_BIOMETRIC);
- startCurrentClient(cookie);
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+ mFingerprint21.startPreparedClient(cookie);
}
@Override // Binder call
public void cancelAuthentication(final IBinder token, final String opPackageName) {
- cancelAuthenticationInternal(token, opPackageName);
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int callingUserId = UserHandle.getCallingUserId();
+
+ if (!canUseFingerprint(opPackageName, true /* requireForeground */, callingUid,
+ callingPid, callingUserId)) {
+ Slog.w(TAG, "cancelAuthentication rejecting package: " + opPackageName);
+ return;
+ }
+
+ mFingerprint21.cancelAuthentication(token);
}
@Override // Binder call
public void cancelAuthenticationFromService(final IBinder token, final String opPackageName,
int callingUid, int callingPid, int callingUserId) {
- checkPermission(MANAGE_BIOMETRIC);
- // Cancellation is from system server in this case.
- cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid,
- callingUserId, false /* fromClient */);
+ Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+ mFingerprint21.cancelAuthentication(token);
}
@Override // Binder call
public void remove(final IBinder token, final int fingerId, final int userId,
- final IFingerprintServiceReceiver receiver, final String opPackageName)
- throws RemoteException {
- checkPermission(MANAGE_FINGERPRINT);
- updateActiveGroup(userId);
-
- if (token == null) {
- Slog.w(TAG, "remove(): token is null");
- return;
- }
-
- final RemovalClient client = new FingerprintRemovalClient(getContext(), mLazyDaemon,
- token, new ClientMonitorCallbackConverter(receiver), fingerId, userId,
- opPackageName, getBiometricUtils(), getSensorId(), mAuthenticatorIds);
- removeInternal(client);
+ final IFingerprintServiceReceiver receiver, final String opPackageName) {
+ Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
+ mFingerprint21.scheduleRemove(token, receiver, fingerId, userId, opPackageName);
}
@Override
public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
final String opPackageName) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- mHandler.post(() -> {
- mLockoutResetTracker.addCallback(callback, opPackageName);
- });
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mLockoutResetDispatcher.addCallback(callback, opPackageName);
}
@Override // Binder call
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
return;
}
@@ -252,23 +194,18 @@
final long ident = Binder.clearCallingIdentity();
try {
if (args.length > 0 && "--proto".equals(args[0])) {
- dumpProto(fd);
+ mFingerprint21.dumpProto(fd);
} else {
- dumpInternal(pw);
+ mFingerprint21.dumpInternal(pw);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
- /**
- * The following methods don't use any common code from BiometricService
- */
-
- // TODO: refactor out common code here
@Override // Binder call
public boolean isHardwareDetected(String opPackageName) {
- if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
+ if (!canUseFingerprint(opPackageName, false /* foregroundOnly */,
Binder.getCallingUid(), Binder.getCallingPid(),
UserHandle.getCallingUserId())) {
return false;
@@ -276,8 +213,7 @@
final long token = Binder.clearCallingIdentity();
try {
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- return daemon != null;
+ return mFingerprint21.isHardwareDetected();
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -285,389 +221,155 @@
@Override // Binder call
public void rename(final int fingerId, final int userId, final String name) {
- checkPermission(MANAGE_FINGERPRINT);
- if (!isCurrentUserOrProfile(userId)) {
+ Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
+ if (!Utils.isCurrentUserOrProfile(getContext(), userId)) {
return;
}
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- getBiometricUtils().renameBiometricForUser(getContext(), userId, fingerId,
- name);
- }
- });
+
+ mFingerprint21.rename(fingerId, userId, name);
}
@Override // Binder call
public List<Fingerprint> getEnrolledFingerprints(int userId, String opPackageName) {
- if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
+ if (!canUseFingerprint(opPackageName, false /* foregroundOnly */,
Binder.getCallingUid(), Binder.getCallingPid(),
UserHandle.getCallingUserId())) {
return Collections.emptyList();
}
- return FingerprintService.this.getEnrolledTemplates(userId);
+ if (userId != UserHandle.getCallingUserId()) {
+ Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
+ }
+ return mFingerprint21.getEnrolledFingerprints(userId);
}
@Override // Binder call
public boolean hasEnrolledFingerprints(int userId, String opPackageName) {
- if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
+ if (!canUseFingerprint(opPackageName, false /* foregroundOnly */,
Binder.getCallingUid(), Binder.getCallingPid(),
UserHandle.getCallingUserId())) {
return false;
}
- return FingerprintService.this.hasEnrolledBiometrics(userId);
+ if (userId != UserHandle.getCallingUserId()) {
+ Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
+ }
+ return mFingerprint21.getEnrolledFingerprints(userId).size() > 0;
}
@Override // Binder call
public @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- return mLockoutTracker.getLockoutModeForUser(userId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ return mFingerprint21.getLockoutModeForUser(userId);
}
@Override // Binder call
- public long getAuthenticatorId(int callingUserId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- return FingerprintService.this.getAuthenticatorId(callingUserId);
+ public long getAuthenticatorId(int userId) {
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ return mFingerprint21.getAuthenticatorId(userId);
}
@Override // Binder call
- public void resetLockout(int userId, byte [] hardwareAuthToken) throws RemoteException {
- checkPermission(RESET_FINGERPRINT_LOCKOUT);
- if (!FingerprintService.this.hasEnrolledBiometrics(userId)) {
- Slog.w(TAG, "Ignoring lockout reset, no templates enrolled for user: " + userId);
- return;
- }
-
- // TODO: confirm security token when we move timeout management into the HAL layer.
- mHandler.post(() -> {
- mLockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId);
- });
+ public void resetLockout(int userId, byte [] hardwareAuthToken) {
+ Utils.checkPermission(getContext(), RESET_FINGERPRINT_LOCKOUT);
+ mFingerprint21.scheduleResetLockout(userId, hardwareAuthToken);
}
@Override
public boolean isClientActive() {
- checkPermission(MANAGE_FINGERPRINT);
- synchronized(FingerprintService.this) {
- return (getCurrentClient() != null) || (getPendingClient() != null);
- }
+ Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
+ return mGestureAvailabilityDispatcher.isAnySensorActive();
}
@Override
public void addClientActiveCallback(IFingerprintClientActiveCallback callback) {
- checkPermission(MANAGE_FINGERPRINT);
- mGestureAvailabilityTracker.registerCallback(callback);
+ Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
+ mGestureAvailabilityDispatcher.registerCallback(callback);
}
@Override
public void removeClientActiveCallback(IFingerprintClientActiveCallback callback) {
- checkPermission(MANAGE_FINGERPRINT);
- mGestureAvailabilityTracker.removeCallback(callback);
+ Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
+ mGestureAvailabilityDispatcher.removeCallback(callback);
}
@Override // Binder call
public void initializeConfiguration(int sensorId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- initializeConfigurationInternal(sensorId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFingerprint21 = new Fingerprint21(getContext(), sensorId, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher);
}
@Override
public void onFingerDown(int x, int y, float minor, float major) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- if (daemon == null) {
- Slog.e(TAG, "onFingerDown | daemon is null");
- } else {
- android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint extension =
- android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint.castFrom(
- daemon);
- if (extension == null) {
- Slog.v(TAG, "onFingerDown | failed to cast the HIDL to V2_3");
- } else {
- try {
- extension.onFingerDown(x, y, minor, major);
- } catch (RemoteException e) {
- Slog.e(TAG, "onFingerDown | RemoteException: ", e);
- }
- }
- }
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFingerprint21.onFingerDown(x, y, minor, major);
}
@Override
public void onFingerUp() {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- if (daemon == null) {
- Slog.e(TAG, "onFingerUp | daemon is null");
- } else {
- android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint extension =
- android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint.castFrom(
- daemon);
- if (extension == null) {
- Slog.v(TAG, "onFingerUp | failed to cast the HIDL to V2_3");
- } else {
- try {
- extension.onFingerUp();
- } catch (RemoteException e) {
- Slog.e(TAG, "onFingerUp | RemoteException: ", e);
- }
- }
- }
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFingerprint21.onFingerUp();
}
@Override
public boolean isUdfps(int sensorId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- IBiometricsFingerprint daemon = getFingerprintDaemon();
- if (daemon == null) {
- Slog.e(TAG, "isUdfps | daemon is null");
- } else {
- android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint extension =
- android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint.castFrom(
- daemon);
- if (extension == null) {
- Slog.v(TAG, "isUdfps | failed to cast the HIDL to V2_3");
- } else {
- try {
- return extension.isUdfps(sensorId);
- } catch (RemoteException e) {
- Slog.e(TAG, "isUdfps | RemoteException: ", e);
- }
- }
- }
- return false;
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ return mFingerprint21.isUdfps();
}
@Override
- public void showUdfpsOverlay() {
- if (mUdfpsOverlayController == null) {
- Slog.e(TAG, "showUdfpsOverlay | mUdfpsOverlayController is null");
- return;
- }
- try {
- mUdfpsOverlayController.showUdfpsOverlay();
- } catch (RemoteException e) {
- Slog.e(TAG, "showUdfpsOverlay | RemoteException: ", e);
- }
- }
-
- @Override
- public void hideUdfpsOverlay() {
- if (mUdfpsOverlayController == null) {
- Slog.e(TAG, "hideUdfpsOverlay | mUdfpsOverlayController is null");
- return;
- }
- try {
- mUdfpsOverlayController.hideUdfpsOverlay();
- } catch (RemoteException e) {
- Slog.e(TAG, "hideUdfpsOverlay | RemoteException: ", e);
- }
- }
-
public void setUdfpsOverlayController(IUdfpsOverlayController controller) {
- mUdfpsOverlayController = controller;
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mFingerprint21.setUdfpsOverlayController(controller);
}
}
- private final LockoutFrameworkImpl mLockoutTracker;
- private IUdfpsOverlayController mUdfpsOverlayController;
-
- @GuardedBy("this")
- private IBiometricsFingerprint mDaemon;
-
- /**
- * Receives callbacks from the HAL.
- */
- private IBiometricsFingerprintClientCallback mDaemonCallback =
- new IBiometricsFingerprintClientCallback.Stub() {
- @Override
- public void onEnrollResult(final long deviceId, final int fingerId, final int groupId,
- final int remaining) {
- mHandler.post(() -> {
- final Fingerprint fingerprint =
- new Fingerprint(getBiometricUtils().getUniqueName(getContext(), groupId),
- groupId, fingerId, deviceId);
- FingerprintService.super.handleEnrollResult(fingerprint, remaining);
- });
- }
-
- @Override
- public void onAcquired(final long deviceId, final int acquiredInfo, final int vendorCode) {
- onAcquired_2_2(deviceId, acquiredInfo, vendorCode);
- }
-
- @Override
- public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) {
- mHandler.post(() -> {
- FingerprintService.super.handleAcquired(acquiredInfo, vendorCode);
- });
- }
-
- @Override
- public void onAuthenticated(final long deviceId, final int fingerId, final int groupId,
- ArrayList<Byte> token) {
- mHandler.post(() -> {
- Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
- FingerprintService.super.handleAuthenticated(fp, token);
- });
- }
-
- @Override
- public void onError(final long deviceId, final int error, final int vendorCode) {
- mHandler.post(() -> {
- FingerprintService.super.handleError(error, vendorCode);
- // TODO: this chunk of code should be common to all biometric services
- if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
- // If we get HW_UNAVAILABLE, try to connect again later...
- Slog.w(TAG, "Got ERROR_HW_UNAVAILABLE; try reconnecting next client.");
- synchronized (this) {
- mDaemon = null;
- mCurrentUserId = UserHandle.USER_NULL;
- }
- }
- });
- }
-
- @Override
- public void onRemoved(final long deviceId, final int fingerId, final int groupId,
- final int remaining) {
- mHandler.post(() -> {
- final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
- FingerprintService.super.handleRemoved(fp, remaining);
- });
- }
-
- @Override
- public void onEnumerate(final long deviceId, final int fingerId, final int groupId,
- final int remaining) {
- mHandler.post(() -> {
- final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
- FingerprintService.super.handleEnumerate(fp, remaining);
- });
-
- }
- };
-
public FingerprintService(Context context) {
super(context);
- mLazyDaemon = FingerprintService.this::getFingerprintDaemon;
- mGestureAvailabilityTracker = new GestureAvailabilityTracker();
- mLockoutResetTracker = new LockoutResetTracker(context);
- final LockoutFrameworkImpl.LockoutResetCallback lockoutResetCallback = userId -> {
- mLockoutResetTracker.notifyLockoutResetCallbacks();
- };
- mLockoutTracker = new LockoutFrameworkImpl(context, lockoutResetCallback);
+ mAppOps = context.getSystemService(AppOpsManager.class);
+ mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
+ mLockoutResetDispatcher = new LockoutResetDispatcher(context);
}
@Override
public void onStart() {
- super.onStart();
publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
- SystemServerInitThreadPool.submit(this::getFingerprintDaemon, TAG + ".onStart");
}
- @Override
- protected String getTag() {
- return TAG;
- }
-
- @Override
- protected IBiometricsFingerprint getDaemon() {
- return getFingerprintDaemon();
- }
-
- @Override
- protected BiometricUtils getBiometricUtils() {
- return FingerprintUtils.getInstance();
- }
-
- @Override
- protected boolean hasReachedEnrollmentLimit(int userId) {
- final int limit = getContext().getResources().getInteger(
- com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
- final int enrolled = FingerprintService.this.getEnrolledTemplates(userId).size();
- if (enrolled >= limit) {
- Slog.w(TAG, "Too many fingerprints registered");
- return true;
- }
- return false;
- }
-
- @Override
- public void serviceDied(long cookie) {
- super.serviceDied(cookie);
- mDaemon = null;
- }
-
- @Override
- protected void updateActiveGroup(int userId) {
- IBiometricsFingerprint daemon = getFingerprintDaemon();
-
- if (daemon != null) {
- try {
- if (userId != mCurrentUserId) {
- int firstSdkInt = Build.VERSION.FIRST_SDK_INT;
- if (firstSdkInt < Build.VERSION_CODES.BASE) {
- Slog.e(TAG, "First SDK version " + firstSdkInt + " is invalid; must be " +
- "at least VERSION_CODES.BASE");
- }
- File baseDir;
- if (firstSdkInt <= Build.VERSION_CODES.O_MR1) {
- baseDir = Environment.getUserSystemDirectory(userId);
- } else {
- baseDir = Environment.getDataVendorDeDirectory(userId);
- }
-
- File fpDir = new File(baseDir, FP_DATA_DIR);
- if (!fpDir.exists()) {
- if (!fpDir.mkdir()) {
- Slog.v(TAG, "Cannot make directory: " + fpDir.getAbsolutePath());
- return;
- }
- // Calling mkdir() from this process will create a directory with our
- // permissions (inherited from the containing dir). This command fixes
- // the label.
- if (!SELinux.restorecon(fpDir)) {
- Slog.w(TAG, "Restorecons failed. Directory will have wrong label.");
- return;
- }
- }
-
- daemon.setActiveGroup(userId, fpDir.getAbsolutePath());
- mCurrentUserId = userId;
- }
- mAuthenticatorIds.put(userId,
- hasEnrolledBiometrics(userId) ? daemon.getAuthenticatorId() : 0L);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to setActiveGroup():", e);
- }
- }
- }
-
- @Override
- protected boolean hasEnrolledBiometrics(int userId) {
- if (userId != UserHandle.getCallingUserId()) {
- checkPermission(INTERACT_ACROSS_USERS);
- }
- return getBiometricUtils().getBiometricsForUser(getContext(), userId).size() > 0;
- }
-
- @Override
- protected String getManageBiometricPermission() {
- return MANAGE_FINGERPRINT;
- }
-
- @Override
- protected void checkUseBiometricPermission() {
+ /**
+ * Checks for public API invocations to ensure that permissions, etc are granted/correct.
+ */
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ private boolean canUseFingerprint(String opPackageName, boolean requireForeground, int uid,
+ int pid, int userId) {
if (getContext().checkCallingPermission(USE_FINGERPRINT)
!= PackageManager.PERMISSION_GRANTED) {
- checkPermission(USE_BIOMETRIC);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC);
}
+
+ if (Binder.getCallingUid() == Process.SYSTEM_UID) {
+ return true; // System process (BiometricService, etc) is always allowed
+ }
+ if (Utils.isKeyguard(getContext(), opPackageName)) {
+ return true;
+ }
+ if (!Utils.isCurrentUserOrProfile(getContext(), userId)) {
+ Slog.w(TAG, "Rejecting " + opPackageName + "; not a current user or profile");
+ return false;
+ }
+ if (!checkAppOps(uid, opPackageName)) {
+ Slog.w(TAG, "Rejecting " + opPackageName + "; permission denied");
+ return false;
+ }
+ if (requireForeground && !(isForegroundActivity(uid, pid))) {
+ Slog.w(TAG, "Rejecting " + opPackageName + "; not in foreground");
+ return false;
+ }
+ return true;
}
- @Override
- protected boolean checkAppOps(int uid, String opPackageName) {
+ private boolean checkAppOps(int uid, String opPackageName) {
boolean appOpsOk = false;
if (mAppOps.noteOp(AppOpsManager.OP_USE_BIOMETRIC, uid, opPackageName)
== AppOpsManager.MODE_ALLOWED) {
@@ -679,154 +381,28 @@
return appOpsOk;
}
- @Override
- protected List<Fingerprint> getEnrolledTemplates(int userId) {
- if (userId != UserHandle.getCallingUserId()) {
- checkPermission(INTERACT_ACROSS_USERS);
+ private boolean isForegroundActivity(int uid, int pid) {
+ try {
+ final List<ActivityManager.RunningAppProcessInfo> procs =
+ ActivityManager.getService().getRunningAppProcesses();
+ if (procs == null) {
+ Slog.e(TAG, "Processes null, defaulting to true");
+ return true;
+ }
+
+ int N = procs.size();
+ for (int i = 0; i < N; i++) {
+ ActivityManager.RunningAppProcessInfo proc = procs.get(i);
+ if (proc.pid == pid && proc.uid == uid
+ && proc.importance <= IMPORTANCE_FOREGROUND_SERVICE) {
+ return true;
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "am.getRunningAppProcesses() failed");
}
- return FingerprintUtils.getInstance().getBiometricsForUser(getContext(), userId);
- }
-
- @Override
- protected void notifyClientActiveCallbacks(boolean isActive) {
- mGestureAvailabilityTracker.notifyClientActiveCallbacks(isActive);
- }
-
- @Override
- protected int statsModality() {
- return BiometricsProtoEnums.MODALITY_FINGERPRINT;
- }
-
- @Override
- protected @LockoutTracker.LockoutMode int getLockoutMode(int userId) {
- return mLockoutTracker.getLockoutModeForUser(userId);
- }
-
- @Override
- protected void doTemplateCleanupForUser(int userId) {
- final List<Fingerprint> enrolledList = getEnrolledTemplates(userId);
- final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
- getContext(), mLazyDaemon, userId, getContext().getOpPackageName(), getSensorId(),
- enrolledList, getBiometricUtils(), mAuthenticatorIds);
- cleanupInternal(client);
- }
-
- /** Gets the fingerprint daemon */
- private synchronized IBiometricsFingerprint getFingerprintDaemon() {
- if (mDaemon == null) {
- Slog.v(TAG, "mDaemon was null, reconnect to fingerprint");
- try {
- mDaemon = IBiometricsFingerprint.getService();
- } catch (java.util.NoSuchElementException e) {
- // Service doesn't exist or cannot be opened. Logged below.
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to get biometric interface", e);
- }
- if (mDaemon == null) {
- Slog.w(TAG, "fingerprint HIDL not available");
- return null;
- }
-
- mDaemon.asBinder().linkToDeath(this, 0);
-
- long halId = 0;
- try {
- halId = mDaemon.setNotify(mDaemonCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to open fingerprint HAL", e);
- mDaemon = null; // try again later!
- }
-
- if (DEBUG) Slog.v(TAG, "Fingerprint HAL id: " + halId);
- if (halId != 0) {
- loadAuthenticatorIds();
- final int userId = ActivityManager.getCurrentUser();
- updateActiveGroup(userId);
- doTemplateCleanupForUser(userId);
- } else {
- Slog.w(TAG, "Failed to open Fingerprint HAL!");
- MetricsLogger.count(getContext(), "fingerprintd_openhal_error", 1);
- mDaemon = null;
- }
- }
- return mDaemon;
+ return false;
}
private native NativeHandle convertSurfaceToNativeHandle(Surface surface);
-
- private void dumpInternal(PrintWriter pw) {
- PerformanceTracker performanceTracker =
- PerformanceTracker.getInstanceForSensorId(getSensorId());
-
- JSONObject dump = new JSONObject();
- try {
- dump.put("service", "Fingerprint Manager");
-
- JSONArray sets = new JSONArray();
- for (UserInfo user : UserManager.get(getContext()).getUsers()) {
- final int userId = user.getUserHandle().getIdentifier();
- final int N = getBiometricUtils().getBiometricsForUser(getContext(), userId).size();
- JSONObject set = new JSONObject();
- set.put("id", userId);
- set.put("count", N);
- set.put("accept", performanceTracker.getAcceptForUser(userId));
- set.put("reject", performanceTracker.getRejectForUser(userId));
- set.put("acquire", performanceTracker.getAcquireForUser(userId));
- set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
- set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
- // cryptoStats measures statistics about secure fingerprint transactions
- // (e.g. to unlock password storage, make secure purchases, etc.)
- set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
- set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
- set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
- sets.put(set);
- }
-
- dump.put("prints", sets);
- } catch (JSONException e) {
- Slog.e(TAG, "dump formatting failure", e);
- }
- pw.println(dump);
- pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
- }
-
- private void dumpProto(FileDescriptor fd) {
- PerformanceTracker tracker =
- PerformanceTracker.getInstanceForSensorId(getSensorId());
-
- final ProtoOutputStream proto = new ProtoOutputStream(fd);
- for (UserInfo user : UserManager.get(getContext()).getUsers()) {
- final int userId = user.getUserHandle().getIdentifier();
-
- final long userToken = proto.start(FingerprintServiceDumpProto.USERS);
-
- proto.write(FingerprintUserStatsProto.USER_ID, userId);
- proto.write(FingerprintUserStatsProto.NUM_FINGERPRINTS,
- getBiometricUtils().getBiometricsForUser(getContext(), userId).size());
-
- // Normal fingerprint authentications (e.g. lockscreen)
- long countsToken = proto.start(FingerprintUserStatsProto.NORMAL);
- proto.write(PerformanceStatsProto.ACCEPT, tracker.getAcceptForUser(userId));
- proto.write(PerformanceStatsProto.REJECT, tracker.getRejectForUser(userId));
- proto.write(PerformanceStatsProto.ACQUIRE, tracker.getAcquireForUser(userId));
- proto.write(PerformanceStatsProto.LOCKOUT, tracker.getTimedLockoutForUser(userId));
- proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT,
- tracker.getPermanentLockoutForUser(userId));
- proto.end(countsToken);
-
- // Statistics about secure fingerprint transactions (e.g. to unlock password
- // storage, make secure purchases, etc.)
- countsToken = proto.start(FingerprintUserStatsProto.CRYPTO);
- proto.write(PerformanceStatsProto.ACCEPT, tracker.getAcceptCryptoForUser(userId));
- proto.write(PerformanceStatsProto.REJECT, tracker.getRejectCryptoForUser(userId));
- proto.write(PerformanceStatsProto.ACQUIRE, tracker.getAcquireCryptoForUser(userId));
- proto.write(PerformanceStatsProto.LOCKOUT, 0); // meaningless for crypto
- proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, 0); // meaningless for crypto
- proto.end(countsToken);
-
- proto.end(userToken);
- }
- proto.flush();
- tracker.clear();
- }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java
new file mode 100644
index 0000000..e1082ae
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2020 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.biometrics.sensors.fingerprint;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.os.Build;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.ClientMonitor;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * Sets the HAL's current active user, and updates the framework's authenticatorId cache.
+ */
+public class FingerprintUpdateActiveUserClient extends ClientMonitor<IBiometricsFingerprint> {
+
+ private static final String TAG = "FingerprintUpdateActiveUserClient";
+ private static final String FP_DATA_DIR = "fpdata";
+
+ private final int mCurrentUserId;
+ private final boolean mHasEnrolledBiometrics;
+ private final Map<Integer, Long> mAuthenticatorIds;
+ private File mDirectory;
+
+ FingerprintUpdateActiveUserClient(@NonNull Context context,
+ @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, int userId,
+ @NonNull String owner, int sensorId, int currentUserId, boolean hasEnrolledBiometrics,
+ @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
+ 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ mCurrentUserId = currentUserId;
+ mHasEnrolledBiometrics = hasEnrolledBiometrics;
+ mAuthenticatorIds = authenticatorIds;
+ }
+
+ @Override
+ public void start(@NonNull FinishCallback finishCallback) {
+ super.start(finishCallback);
+
+ if (mCurrentUserId == getTargetUserId()) {
+ Slog.d(TAG, "Already user: " + mCurrentUserId + ", refreshing authenticatorId");
+ try {
+ mAuthenticatorIds.put(getTargetUserId(), mHasEnrolledBiometrics
+ ? getFreshDaemon().getAuthenticatorId() : 0L);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to refresh authenticatorId", e);
+ }
+ finishCallback.onClientFinished(this, true /* success */);
+ return;
+ }
+
+ int firstSdkInt = Build.VERSION.FIRST_SDK_INT;
+ if (firstSdkInt < Build.VERSION_CODES.BASE) {
+ Slog.e(TAG, "First SDK version " + firstSdkInt + " is invalid; must be " +
+ "at least VERSION_CODES.BASE");
+ }
+ File baseDir;
+ if (firstSdkInt <= Build.VERSION_CODES.O_MR1) {
+ baseDir = Environment.getUserSystemDirectory(getTargetUserId());
+ } else {
+ baseDir = Environment.getDataVendorDeDirectory(getTargetUserId());
+ }
+
+ mDirectory = new File(baseDir, FP_DATA_DIR);
+ if (!mDirectory.exists()) {
+ if (!mDirectory.mkdir()) {
+ Slog.e(TAG, "Cannot make directory: " + mDirectory.getAbsolutePath());
+ finishCallback.onClientFinished(this, false /* success */);
+ return;
+ }
+ // Calling mkdir() from this process will create a directory with our
+ // permissions (inherited from the containing dir). This command fixes
+ // the label.
+ if (!SELinux.restorecon(mDirectory)) {
+ Slog.e(TAG, "Restorecons failed. Directory will have wrong label.");
+ finishCallback.onClientFinished(this, false /* success */);
+ return;
+ }
+ }
+
+ startHalOperation();
+ }
+
+ @Override
+ public void unableToStart() {
+ // Nothing to do here
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ getFreshDaemon().setActiveGroup(getTargetUserId(), mDirectory.getAbsolutePath());
+ mAuthenticatorIds.put(getTargetUserId(), mHasEnrolledBiometrics
+ ? getFreshDaemon().getAuthenticatorId() : 0L);
+ mFinishCallback.onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to setActiveGroup: " + e);
+ mFinishCallback.onClientFinished(this, false /* success */);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityDispatcher.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityDispatcher.java
new file mode 100644
index 0000000..7bda33d
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityDispatcher.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 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.biometrics.sensors.fingerprint;
+
+import android.hardware.fingerprint.IFingerprintClientActiveCallback;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Keeps track of sensor gesture availability (e.g. swipe), and notifies clients when its
+ * availability changes
+ */
+public class GestureAvailabilityDispatcher {
+ private static final String TAG = "GestureAvailabilityTracker";
+
+ private final CopyOnWriteArrayList<IFingerprintClientActiveCallback> mClientActiveCallbacks;
+ private final Map<Integer, Boolean> mActiveSensors;
+
+ private boolean mIsActive;
+
+ GestureAvailabilityDispatcher() {
+ mClientActiveCallbacks = new CopyOnWriteArrayList<>();
+ mActiveSensors = new HashMap<>();
+ }
+
+ /**
+ * @return true if any sensor is active.
+ */
+ public boolean isAnySensorActive() {
+ return mIsActive;
+ }
+
+ public void markSensorActive(int sensorId, boolean active) {
+ mActiveSensors.put(sensorId, active);
+
+ final boolean wasActive = mIsActive;
+ boolean isActive = false;
+ for (Boolean b : mActiveSensors.values()) {
+ if (b) {
+ isActive = true;
+ break;
+ }
+ }
+
+ if (wasActive != isActive) {
+ Slog.d(TAG, "Notifying gesture availability, active=" + mIsActive);
+ mIsActive = isActive;
+ notifyClientActiveCallbacks(mIsActive);
+ }
+ }
+
+ void registerCallback(IFingerprintClientActiveCallback callback) {
+ mClientActiveCallbacks.add(callback);
+ }
+
+ void removeCallback(IFingerprintClientActiveCallback callback) {
+ mClientActiveCallbacks.remove(callback);
+ }
+
+ private void notifyClientActiveCallbacks(boolean isActive) {
+ for (IFingerprintClientActiveCallback callback : mClientActiveCallbacks) {
+ try {
+ callback.onClientActiveChanged(isActive);
+ } catch (RemoteException re) {
+ // If the remote is dead, stop notifying it
+ mClientActiveCallbacks.remove(callback);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityTracker.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityTracker.java
deleted file mode 100644
index 6292ecf..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/GestureAvailabilityTracker.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint;
-
-import android.hardware.fingerprint.IFingerprintClientActiveCallback;
-import android.os.RemoteException;
-
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * Keeps track of sensor gesture availability (e.g. swipe), and notifies clients when its
- * availability changes
- */
-class GestureAvailabilityTracker {
- private final CopyOnWriteArrayList<IFingerprintClientActiveCallback> mClientActiveCallbacks =
- new CopyOnWriteArrayList<>();
-
- void registerCallback(IFingerprintClientActiveCallback callback) {
- mClientActiveCallbacks.add(callback);
- }
-
- void removeCallback(IFingerprintClientActiveCallback callback) {
- mClientActiveCallbacks.remove(callback);
- }
-
- void notifyClientActiveCallbacks(boolean isActive) {
- for (IFingerprintClientActiveCallback callback : mClientActiveCallbacks) {
- try {
- callback.onClientActiveChanged(isActive);
- } catch (RemoteException re) {
- // If the remote is dead, stop notifying it
- mClientActiveCallbacks.remove(callback);
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/Cancellable.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
similarity index 61%
copy from services/core/java/com/android/server/biometrics/sensors/Cancellable.java
copy to services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
index 68a3bc6..e0806ff 100644
--- a/services/core/java/com/android/server/biometrics/sensors/Cancellable.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.server.biometrics.sensors;
+package com.android.server.biometrics.sensors.fingerprint;
/**
- * Interface that cancellable {@link ClientMonitor} subclasses should implement.
+ * Interface for under-display fingerprint sensors.
+ * {@link com.android.server.biometrics.sensors.ClientMonitor} subclass that require knowledge of
+ * finger position (e.g. enroll, authenticate) should implement this.
*/
-public interface Cancellable {
- /**
- * Requests to end the ClientMonitor's lifecycle.
- */
- void cancel();
+public interface Udfps {
+ void onFingerDown(int x, int y, float minor, float major);
+ void onFingerUp();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
new file mode 100644
index 0000000..e6e1533
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 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.biometrics.sensors.fingerprint;
+
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * Contains helper methods for under-display fingerprint HIDL.
+ */
+public class UdfpsHelper {
+
+ private static final String TAG = "UdfpsHelper";
+
+ static void onFingerDown(IBiometricsFingerprint daemon, int x, int y, float minor,
+ float major) {
+ android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint extension =
+ android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint.castFrom(
+ daemon);
+ if (extension == null) {
+ Slog.v(TAG, "onFingerDown | failed to cast the HIDL to V2_3");
+ return;
+ }
+
+ try {
+ extension.onFingerDown(x, y, minor, major);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onFingerDown | RemoteException: ", e);
+ }
+ }
+
+ static void onFingerUp(IBiometricsFingerprint daemon) {
+ android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint extension =
+ android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint.castFrom(
+ daemon);
+ if (extension == null) {
+ Slog.v(TAG, "onFingerUp | failed to cast the HIDL to V2_3");
+ return;
+ }
+
+ try {
+ extension.onFingerUp();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onFingerUp | RemoteException: ", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
index 023ed46..bcf63dc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
@@ -18,17 +18,12 @@
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.iris.IIrisService;
-import com.android.server.biometrics.sensors.BiometricServiceBase;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintService;
-
-import java.util.List;
+import com.android.server.SystemService;
+import com.android.server.biometrics.Utils;
/**
* A service to manage multiple clients that want to access the Iris HAL API.
@@ -36,11 +31,9 @@
* iris-related events.
*
* TODO: The vendor is expected to fill in the service. See
- * {@link FingerprintService}
- *
- * @hide
+ * {@link com.android.server.biometrics.sensors.face.FaceService}
*/
-public class IrisService extends BiometricServiceBase {
+public class IrisService extends SystemService {
private static final String TAG = "IrisService";
@@ -50,92 +43,16 @@
private final class IrisServiceWrapper extends IIrisService.Stub {
@Override // Binder call
public void initializeConfiguration(int sensorId) {
- checkPermission(USE_BIOMETRIC_INTERNAL);
- initializeConfigurationInternal(sensorId);
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
}
}
- /**
- * Initializes the system service.
- * <p>
- * Subclasses must define a single argument constructor that accepts the context
- * and passes it to super.
- * </p>
- *
- * @param context The system server context.
- */
- public IrisService(Context context) {
+ public IrisService(@NonNull Context context) {
super(context);
}
@Override
public void onStart() {
- super.onStart();
publishBinderService(Context.IRIS_SERVICE, new IrisServiceWrapper());
}
-
- @Override
- protected void doTemplateCleanupForUser(int userId) {
-
- }
-
- @Override
- protected String getTag() {
- return TAG;
- }
-
- @Override
- protected Object getDaemon() {
- return null;
- }
-
- @Override
- protected BiometricUtils getBiometricUtils() {
- return null;
- }
-
- @Override
- protected boolean hasReachedEnrollmentLimit(int userId) {
- return false;
- }
-
- @Override
- protected void updateActiveGroup(int userId) {
-
- }
-
- @Override
- protected boolean hasEnrolledBiometrics(int userId) {
- return false;
- }
-
- @Override
- protected String getManageBiometricPermission() {
- return null;
- }
-
- @Override
- protected void checkUseBiometricPermission() {
-
- }
-
- @Override
- protected boolean checkAppOps(int uid, String opPackageName) {
- return false;
- }
-
- @Override
- protected List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates(int userId) {
- return null;
- }
-
- @Override
- protected int statsModality() {
- return BiometricsProtoEnums.MODALITY_IRIS;
- }
-
- @Override
- protected int getLockoutMode(int userId) {
- return LockoutTracker.LOCKOUT_NONE;
- }
}
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index b279b37..ed3a223 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -400,7 +400,7 @@
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage,
- intendingUid, intendingUserId)
+ intendingUid, intendingUserId, false)
|| isDeviceLocked(intendingUserId)) {
return null;
}
@@ -416,7 +416,7 @@
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage,
- intendingUid, intendingUserId)
+ intendingUid, intendingUserId, false)
|| isDeviceLocked(intendingUserId)) {
return false;
}
@@ -450,7 +450,7 @@
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage,
- intendingUid, intendingUserId)
+ intendingUid, intendingUserId, false)
|| isDeviceLocked(intendingUserId)) {
return false;
}
@@ -740,14 +740,21 @@
private boolean clipboardAccessAllowed(int op, String callingPackage, int uid,
@UserIdInt int userId) {
- // Check the AppOp.
- if (mAppOps.noteOp(op, uid, callingPackage) != AppOpsManager.MODE_ALLOWED) {
- return false;
- }
+ return clipboardAccessAllowed(op, callingPackage, uid, userId, true);
+ }
+
+ private boolean clipboardAccessAllowed(int op, String callingPackage, int uid,
+ @UserIdInt int userId, boolean shouldNoteOp) {
+
+ boolean allowed = false;
+
+ // First, verify package ownership to ensure use below is safe.
+ mAppOps.checkPackage(uid, callingPackage);
+
// Shell can access the clipboard for testing purposes.
if (mPm.checkPermission(android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND,
callingPackage) == PackageManager.PERMISSION_GRANTED) {
- return true;
+ allowed = true;
}
// The default IME is always allowed to access the clipboard.
String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(),
@@ -755,7 +762,7 @@
if (!TextUtils.isEmpty(defaultIme)) {
final String imePkg = ComponentName.unflattenFromString(defaultIme).getPackageName();
if (imePkg.equals(callingPackage)) {
- return true;
+ allowed = true;
}
}
@@ -766,8 +773,10 @@
// at the same time. e.x. SystemUI. It needs to check the window focus of
// Binder.getCallingUid(). Without checking, the user X can't copy any thing from
// INTERNAL_SYSTEM_WINDOW to the other applications.
- boolean allowed = mWm.isUidFocused(uid)
- || isInternalSysWindowAppWithWindowFocus(callingPackage);
+ if (!allowed) {
+ allowed = mWm.isUidFocused(uid)
+ || isInternalSysWindowAppWithWindowFocus(callingPackage);
+ }
if (!allowed && mContentCaptureInternal != null) {
// ...or the Content Capture Service
// The uid parameter of mContentCaptureInternal.isContentCaptureServiceForUser
@@ -786,17 +795,28 @@
// userId must pass intending userId. i.e. user#10.
allowed = mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId);
}
- if (!allowed) {
- Slog.e(TAG, "Denying clipboard access to " + callingPackage
- + ", application is not in focus neither is a system service for "
- + "user " + userId);
- }
- return allowed;
+ break;
case AppOpsManager.OP_WRITE_CLIPBOARD:
// Writing is allowed without focus.
- return true;
+ allowed = true;
+ break;
default:
throw new IllegalArgumentException("Unknown clipboard appop " + op);
}
+ if (!allowed) {
+ Slog.e(TAG, "Denying clipboard access to " + callingPackage
+ + ", application is not in focus nor is it a system service for "
+ + "user " + userId);
+ return false;
+ }
+ // Finally, check the app op.
+ int appOpsResult;
+ if (shouldNoteOp) {
+ appOpsResult = mAppOps.noteOp(op, uid, callingPackage);
+ } else {
+ appOpsResult = mAppOps.checkOp(op, uid, callingPackage);
+ }
+
+ return appOpsResult == AppOpsManager.MODE_ALLOWED;
}
}
diff --git a/services/core/java/com/android/server/input/InputShellCommand.java b/services/core/java/com/android/server/input/InputShellCommand.java
index 533392a..fd5f48c 100644
--- a/services/core/java/com/android/server/input/InputShellCommand.java
+++ b/services/core/java/com/android/server/input/InputShellCommand.java
@@ -199,7 +199,7 @@
+ " (Default: touchscreen)");
out.println(" press (Default: trackball)");
out.println(" roll <dx> <dy> (Default: trackball)");
- out.println(" motionevent <DOWN|UP|MOVE> <x> <y> (Default: touchscreen)");
+ out.println(" motionevent <DOWN|UP|MOVE|CANCEL> <x> <y> (Default: touchscreen)");
}
}
@@ -357,32 +357,50 @@
displayId);
}
- private void runMotionEvent(int inputSource, int displayId) {
- inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
- sendMotionEvent(inputSource, getNextArgRequired(), Float.parseFloat(getNextArgRequired()),
- Float.parseFloat(getNextArgRequired()), displayId);
+ private int getAction() {
+ String actionString = getNextArgRequired();
+ switch (actionString.toUpperCase()) {
+ case "DOWN":
+ return MotionEvent.ACTION_DOWN;
+ case "UP":
+ return MotionEvent.ACTION_UP;
+ case "MOVE":
+ return MotionEvent.ACTION_MOVE;
+ case "CANCEL":
+ return MotionEvent.ACTION_CANCEL;
+ default:
+ throw new IllegalArgumentException("Unknown action: " + actionString);
+ }
}
- private void sendMotionEvent(int inputSource, String motionEventType, float x, float y,
- int displayId) {
- final int action;
- final float pressure;
+ private void runMotionEvent(int inputSource, int displayId) {
+ inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
+ int action = getAction();
+ float x = 0, y = 0;
+ if (action == MotionEvent.ACTION_DOWN
+ || action == MotionEvent.ACTION_MOVE
+ || action == MotionEvent.ACTION_UP) {
+ x = Float.parseFloat(getNextArgRequired());
+ y = Float.parseFloat(getNextArgRequired());
+ } else {
+ // For ACTION_CANCEL, the positions are optional
+ String xString = getNextArg();
+ String yString = getNextArg();
+ if (xString != null && yString != null) {
+ x = Float.parseFloat(xString);
+ y = Float.parseFloat(yString);
+ }
+ }
- switch (motionEventType.toUpperCase()) {
- case "DOWN":
- action = MotionEvent.ACTION_DOWN;
- pressure = DEFAULT_PRESSURE;
- break;
- case "UP":
- action = MotionEvent.ACTION_UP;
- pressure = NO_PRESSURE;
- break;
- case "MOVE":
- action = MotionEvent.ACTION_MOVE;
- pressure = DEFAULT_PRESSURE;
- break;
- default:
- throw new IllegalArgumentException("Unknown motionevent " + motionEventType);
+ sendMotionEvent(inputSource, action, x, y, displayId);
+ }
+
+ private void sendMotionEvent(int inputSource, int action, float x, float y,
+ int displayId) {
+ float pressure = NO_PRESSURE;
+
+ if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) {
+ pressure = DEFAULT_PRESSURE;
}
final long now = SystemClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d8ee32e..0154fe0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -18,8 +18,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_SAME_EDITOR;
-
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.Manifest;
@@ -719,11 +717,6 @@
*/
int mImeWindowVis;
- /**
- * Checks if the client needs to start input.
- */
- private boolean mCurClientNeedStartInput = false;
-
private AlertDialog.Builder mDialogBuilder;
private AlertDialog mSwitchingDialog;
private IBinder mSwitchingDialogToken = new Binder();
@@ -3467,20 +3460,14 @@
if (mCurFocusedWindow == windowToken) {
if (DEBUG) {
Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
- + " attribute=" + attribute + ", token = " + windowToken);
+ + " attribute=" + attribute + ", token = " + windowToken
+ + ", startInputReason="
+ + InputMethodDebug.startInputReasonToString(startInputReason));
}
- // Needs to start input when the same window focus gain but not with the same editor,
- // or when the current client needs to start input (e.g. when focusing the same
- // window after device turned screen on).
- if (attribute != null && (startInputReason != WINDOW_FOCUS_GAIN_REPORT_WITH_SAME_EDITOR
- || mCurClientNeedStartInput)) {
- if (mIsInteractive) {
- mCurClientNeedStartInput = false;
- }
+ if (attribute != null) {
return startInputUncheckedLocked(cs, inputContext, missingMethods,
attribute, startInputFlags, startInputReason);
}
-
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
null, null, null, -1, null);
@@ -4459,9 +4446,6 @@
private void handleSetInteractive(final boolean interactive) {
synchronized (mMethodMap) {
mIsInteractive = interactive;
- if (!interactive) {
- mCurClientNeedStartInput = true;
- }
updateSystemUiLocked(interactive ? mImeWindowVis : 0, mBackDisposition);
// Inform the current client of the change in active status
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index 8843e52..2bcee2f 100644
--- a/services/core/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.location.Location;
import android.location.util.identity.CallerIdentity;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -128,27 +129,38 @@
mServiceWatcher.dump(fd, pw, args);
}
+ private static String guessPackageName(Context context, int uid) {
+ String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
+ if (packageNames == null || packageNames.length == 0) {
+ // illegal state exception will propagate back through binders
+ throw new IllegalStateException(
+ "location provider from uid " + uid + " has no package information");
+ } else {
+ return packageNames[0];
+ }
+ }
+
private class Proxy extends ILocationProviderManager.Stub {
Proxy() {}
// executed on binder thread
@Override
- public void onSetAttributionTag(String attributionTag) {
+ public void onSetIdentity(@Nullable String packageName, @Nullable String attributionTag) {
synchronized (mLock) {
if (mProxy != this) {
return;
}
- String packageName = mServiceWatcher.getBoundService().getPackageName();
+ CallerIdentity identity;
if (packageName == null) {
- return;
+ packageName = guessPackageName(mContext, Binder.getCallingUid());
+ // unsafe is ok since the package is coming direct from the package manager here
+ identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag);
+ } else {
+ identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
}
- // we don't need to verify the package name because we're getting it straight from
- // the service watcher
- CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName,
- attributionTag);
setIdentity(identity);
}
}
@@ -163,12 +175,9 @@
// if no identity is set yet, set it now
if (getIdentity() == null) {
- String packageName = mServiceWatcher.getBoundService().getPackageName();
- if (packageName != null) {
- // we don't need to verify the package name because we're getting it
- // straight from the service watcher
- setIdentity(CallerIdentity.fromBinderUnsafe(packageName, null));
- }
+ String packageName = guessPackageName(mContext, Binder.getCallingUid());
+ // unsafe is ok since the package is coming direct from the package manager here
+ setIdentity(CallerIdentity.fromBinderUnsafe(packageName, null));
}
setProperties(properties);
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 9a83242..0eba69e 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -16,7 +16,8 @@
package com.android.server.media;
-import static android.os.UserHandle.USER_ALL;
+import static android.os.UserHandle.ALL;
+import static android.os.UserHandle.CURRENT;
import static com.android.server.media.MediaKeyDispatcher.KEY_EVENT_LONG_PRESS;
import static com.android.server.media.MediaKeyDispatcher.isDoubleTapOverridden;
@@ -251,7 +252,7 @@
private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
List<MediaSessionRecord> records = new ArrayList<>();
- if (userId == USER_ALL) {
+ if (userId == ALL.getIdentifier()) {
int size = mUserRecords.size();
for (int i = 0; i < size; i++) {
records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
@@ -267,7 +268,8 @@
// Return global priority session at the first whenever it's asked.
if (isGlobalPriorityActiveLocked()
- && (userId == USER_ALL || userId == mGlobalPrioritySession.getUserId())) {
+ && (userId == ALL.getIdentifier()
+ || userId == mGlobalPrioritySession.getUserId())) {
records.add(0, mGlobalPrioritySession);
}
return records;
@@ -275,7 +277,7 @@
List<Session2Token> getSession2TokensLocked(int userId) {
List<Session2Token> list = new ArrayList<>();
- if (userId == USER_ALL) {
+ if (userId == ALL.getIdentifier()) {
int size = mUserRecords.size();
for (int i = 0; i < size; i++) {
list.addAll(mUserRecords.valueAt(i).mPriorityStack.getSession2Tokens(userId));
@@ -351,7 +353,7 @@
FullUserRecord user = getFullUserRecordLocked(userId);
if (user != null) {
if (user.mFullUserId == userId) {
- user.destroySessionsForUserLocked(USER_ALL);
+ user.destroySessionsForUserLocked(ALL.getIdentifier());
mUserRecords.remove(userId);
} else {
user.destroySessionsForUserLocked(userId);
@@ -653,7 +655,7 @@
pushRemoteVolumeUpdateLocked(userId);
for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
SessionsListenerRecord record = mSessionsListeners.get(i);
- if (record.userId == USER_ALL || record.userId == userId) {
+ if (record.userId == ALL.getIdentifier() || record.userId == userId) {
try {
record.listener.onActiveSessionsChanged(tokens);
} catch (RemoteException e) {
@@ -668,13 +670,13 @@
void pushSession2Changed(int userId) {
synchronized (mLock) {
- List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL);
+ List<Session2Token> allSession2Tokens = getSession2TokensLocked(ALL.getIdentifier());
List<Session2Token> session2Tokens = getSession2TokensLocked(userId);
for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i);
try {
- if (listenerRecord.userId == USER_ALL) {
+ if (listenerRecord.userId == ALL.getIdentifier()) {
listenerRecord.listener.onSession2TokensChanged(allSession2Tokens);
} else if (listenerRecord.userId == userId) {
listenerRecord.listener.onSession2TokensChanged(session2Tokens);
@@ -760,7 +762,8 @@
}
private MediaSessionRecord getMediaSessionRecordLocked(MediaSession.Token sessionToken) {
- FullUserRecord user = getFullUserRecordLocked(UserHandle.getUserId(sessionToken.getUid()));
+ FullUserRecord user = getFullUserRecordLocked(
+ UserHandle.getUserHandleForUid(sessionToken.getUid()).getIdentifier());
if (user != null) {
return user.mPriorityStack.getMediaSessionRecord(sessionToken);
}
@@ -1089,7 +1092,7 @@
private void observe() {
mContentResolver.registerContentObserver(mSecureSettingsUri,
- false, this, USER_ALL);
+ false, this, ALL.getIdentifier());
}
@Override
@@ -1435,7 +1438,7 @@
final IOnMediaKeyEventDispatchedListener listener) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(uid);
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
if (!hasMediaControlPermission(pid, uid)) {
@@ -1463,7 +1466,7 @@
final IOnMediaKeyEventDispatchedListener listener) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(uid);
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
if (!hasMediaControlPermission(pid, uid)) {
@@ -1491,7 +1494,7 @@
final IOnMediaKeyEventSessionChangedListener listener) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(uid);
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
if (!hasMediaControlPermission(pid, uid)) {
@@ -1519,7 +1522,7 @@
final IOnMediaKeyEventSessionChangedListener listener) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(uid);
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
if (!hasMediaControlPermission(pid, uid)) {
@@ -1557,7 +1560,7 @@
}
synchronized (mLock) {
- int userId = UserHandle.getUserId(uid);
+ int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
FullUserRecord user = getFullUserRecordLocked(userId);
if (user == null || user.mFullUserId != userId) {
Log.w(TAG, "Only the full user can set the volume key long-press listener"
@@ -1616,7 +1619,7 @@
}
synchronized (mLock) {
- int userId = UserHandle.getUserId(uid);
+ int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
FullUserRecord user = getFullUserRecordLocked(userId);
if (user == null || user.mFullUserId != userId) {
Log.w(TAG, "Only the full user can set the media key listener"
@@ -1742,10 +1745,7 @@
}
if (down || up) {
int flags = AudioManager.FLAG_FROM_KEY;
- if (musicOnly) {
- // This flag is used when the screen is off to only affect active media.
- flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
- } else {
+ if (!musicOnly) {
// These flags are consistent with the home screen
if (up) {
flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
@@ -1759,11 +1759,12 @@
direction = 0;
}
dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid,
- asSystemService, stream, direction, flags);
+ asSystemService, stream, direction, flags, musicOnly);
} else if (isMute) {
if (down && keyEvent.getRepeatCount() == 0) {
dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid,
- asSystemService, stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);
+ asSystemService, stream, AudioManager.ADJUST_TOGGLE_MUTE, flags,
+ musicOnly);
}
}
}
@@ -1846,7 +1847,7 @@
try {
synchronized (mLock) {
dispatchAdjustVolumeLocked(packageName, opPackageName, pid, uid, false,
- suggestedStream, delta, flags);
+ suggestedStream, delta, flags, false);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -1924,7 +1925,7 @@
public boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid)
throws RemoteException {
final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(uid);
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
// Don't perform sanity check between controllerPackageName and controllerUid.
@@ -2003,7 +2004,7 @@
private boolean hasEnabledNotificationListener(int resolvedUserId, String packageName)
throws RemoteException {
// You may not access another user's content as an enabled listener.
- final int userId = UserHandle.getUserId(resolvedUserId);
+ final int userId = UserHandle.getUserHandleForUid(resolvedUserId).getIdentifier();
if (resolvedUserId != userId) {
return false;
}
@@ -2028,7 +2029,8 @@
}
private void dispatchAdjustVolumeLocked(String packageName, String opPackageName, int pid,
- int uid, boolean asSystemService, int suggestedStream, int direction, int flags) {
+ int uid, boolean asSystemService, int suggestedStream, int direction, int flags,
+ boolean musicOnly) {
MediaSessionRecordImpl session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
: mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
@@ -2043,8 +2045,7 @@
+ ". flags=" + flags + ", preferSuggestedStream="
+ preferSuggestedStream + ", session=" + session);
}
- if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
- && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
+ if (musicOnly && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
if (DEBUG_KEY_EVENT) {
Log.d(TAG, "Nothing is playing on the music stream. Skipping volume event,"
+ " flags=" + flags);
@@ -2220,7 +2221,7 @@
private boolean isUserSetupComplete() {
return Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
+ Settings.Secure.USER_SETUP_COMPLETE, 0, CURRENT.getIdentifier()) != 0;
}
// we only handle public stream types, which are 0-5
diff --git a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
index 5c45e67..db553ee 100644
--- a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
+++ b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
@@ -22,7 +22,6 @@
import android.content.Context;
import android.os.Looper;
import android.telephony.Annotation;
-import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
@@ -197,19 +196,7 @@
@Override
public void onServiceStateChanged(@NonNull ServiceState ss) {
- // In 5G SA (Stand Alone) mode, the primary cell itself will be 5G hence telephony
- // would report RAT = 5G_NR.
- // However, in 5G NSA (Non Stand Alone) mode, the primary cell is still LTE and
- // network allocates a secondary 5G cell so telephony reports RAT = LTE along with
- // NR state as connected. In such case, attributes the data usage to NR.
- // See b/160727498.
- final boolean is5GNsa = (ss.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE
- || ss.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE_CA)
- && ss.getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED;
-
- final int networkType =
- (is5GNsa ? TelephonyManager.NETWORK_TYPE_NR : ss.getDataNetworkType());
-
+ final int networkType = ss.getDataNetworkType();
final int collapsedRatType = getCollapsedRatType(networkType);
if (collapsedRatType == mLastCollapsedRatType) return;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e2be193..88964e0 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4674,10 +4674,11 @@
@Override
public void setNotificationPolicy(String pkg, Policy policy) {
enforcePolicyAccess(pkg, "setNotificationPolicy");
+ int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
final ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pkg,
- 0, UserHandle.getUserId(MY_UID));
+ 0, UserHandle.getUserId(callingUid));
Policy currPolicy = mZenModeHelper.getNotificationPolicy();
if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.P) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index dd6a83b..9cf9545 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -75,6 +75,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -172,6 +173,8 @@
private boolean mAllowInvalidShortcuts = false;
+ private Map<String, List<String>> mOemLockedApps = new HashMap();
+
public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
ZenModeHelper zenHelper, NotificationChannelLogger notificationChannelLogger,
AppOpsManager appOpsManager,
@@ -314,6 +317,12 @@
}
channel.setImportanceLockedByCriticalDeviceFunction(
r.defaultAppLockedImportance);
+ channel.setImportanceLockedByOEM(r.oemLockedImportance);
+ if (!channel.isImportanceLockedByOEM()) {
+ if (r.oemLockedChannels.contains(channel.getId())) {
+ channel.setImportanceLockedByOEM(true);
+ }
+ }
boolean isInvalidShortcutChannel =
channel.getConversationId() != null &&
channel.getConversationId().contains(
@@ -396,6 +405,14 @@
r.visibility = visibility;
r.showBadge = showBadge;
r.bubblePreference = bubblePreference;
+ if (mOemLockedApps.containsKey(r.pkg)) {
+ List<String> channels = mOemLockedApps.get(r.pkg);
+ if (channels == null || channels.isEmpty()) {
+ r.oemLockedImportance = true;
+ } else {
+ r.oemLockedChannels = channels;
+ }
+ }
try {
createDefaultChannelIfNeededLocked(r);
@@ -1149,8 +1166,10 @@
String channelId = appSplit.length == 2 ? appSplit[1] : null;
synchronized (mPackagePreferences) {
+ boolean foundApp = false;
for (PackagePreferences r : mPackagePreferences.values()) {
if (r.pkg.equals(appName)) {
+ foundApp = true;
if (channelId == null) {
// lock all channels for the app
r.oemLockedImportance = true;
@@ -1168,6 +1187,14 @@
}
}
}
+ if (!foundApp) {
+ List<String> channels =
+ mOemLockedApps.getOrDefault(appName, new ArrayList<>());
+ if (channelId != null) {
+ channels.add(channelId);
+ }
+ mOemLockedApps.put(appName, channels);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ca16d57..a864aa6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -403,6 +403,7 @@
@GuardedBy("mLock")
private boolean mVerityFound;
+ @GuardedBy("mLock")
private boolean mDataLoaderFinished = false;
@GuardedBy("mLock")
@@ -1118,7 +1119,7 @@
if (hasParentSessionId()) {
throw new IllegalStateException(
"Session " + sessionId + " is a child of multi-package session "
- + mParentSessionId + " and may not be committed directly.");
+ + getParentSessionId() + " and may not be committed directly.");
}
if (!markAsSealed(statusReceiver, forTransfer)) {
@@ -1589,12 +1590,12 @@
}
private void onStorageUnhealthy() {
- if (TextUtils.isEmpty(mPackageName)) {
+ final String packageName = getPackageName();
+ if (TextUtils.isEmpty(packageName)) {
// The package has not been installed.
return;
}
final PackageManagerService packageManagerService = mPm;
- final String packageName = mPackageName;
mHandler.post(() -> {
if (packageManagerService.deletePackageX(packageName,
PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM,
@@ -1686,7 +1687,11 @@
}
private void handleInstall() {
- if (isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked()) {
+ final boolean needsLogging;
+ synchronized (mLock) {
+ needsLogging = isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked();
+ }
+ if (needsLogging) {
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.INSTALL_PACKAGE)
.setAdmin(mInstallSource.installerPackageName)
@@ -1929,19 +1934,20 @@
// Skip logging the side-loaded app installations, as those are private and aren't reported
// anywhere; app stores already have a record of the installation and that's why reporting
// it here is fine
+ final String packageName = getPackageName();
final String packageNameToLog =
- (params.installFlags & PackageManager.INSTALL_FROM_ADB) == 0 ? mPackageName : "";
+ (params.installFlags & PackageManager.INSTALL_FROM_ADB) == 0 ? packageName : "";
final long currentTimestamp = System.currentTimeMillis();
FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLER_V2_REPORTED,
isIncrementalInstallation(),
packageNameToLog,
currentTimestamp - createdMillis,
returnCode,
- getApksSize());
+ getApksSize(packageName));
}
- private long getApksSize() {
- final PackageSetting ps = mPm.getPackageSetting(mPackageName);
+ private long getApksSize(String packageName) {
+ final PackageSetting ps = mPm.getPackageSetting(packageName);
if (ps == null) {
return 0;
}
@@ -2577,7 +2583,7 @@
}
void setPermissionsResult(boolean accepted) {
- if (!mSealed) {
+ if (!isSealed()) {
throw new SecurityException("Must be sealed to accept permissions");
}
@@ -2653,7 +2659,7 @@
if (hasParentSessionId()) {
throw new IllegalStateException(
"Session " + sessionId + " is a child of multi-package session "
- + mParentSessionId + " and may not be abandoned directly.");
+ + getParentSessionId() + " and may not be abandoned directly.");
}
List<PackageInstallerSession> childSessions = getChildSessionsNotLocked();
@@ -2823,7 +2829,11 @@
return;
}
- if (mDestroyed || mDataLoaderFinished) {
+ final boolean isDestroyedOrDataLoaderFinished;
+ synchronized (mLock) {
+ isDestroyedOrDataLoaderFinished = mDestroyed || mDataLoaderFinished;
+ }
+ if (isDestroyedOrDataLoaderFinished) {
switch (status) {
case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE:
onStorageUnhealthy();
@@ -2835,7 +2845,9 @@
try {
IDataLoader dataLoader = dataLoaderManager.getDataLoader(dataLoaderId);
if (dataLoader == null) {
- mDataLoaderFinished = true;
+ synchronized (mLock) {
+ mDataLoaderFinished = true;
+ }
dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
"Failure to obtain data loader");
return;
@@ -2868,10 +2880,12 @@
break;
}
case IDataLoaderStatusListener.DATA_LOADER_IMAGE_READY: {
- mDataLoaderFinished = true;
+ synchronized (mLock) {
+ mDataLoaderFinished = true;
+ }
if (hasParentSessionId()) {
mSessionProvider.getSession(
- mParentSessionId).dispatchStreamValidateAndCommit();
+ getParentSessionId()).dispatchStreamValidateAndCommit();
} else {
dispatchStreamValidateAndCommit();
}
@@ -2881,7 +2895,9 @@
break;
}
case IDataLoaderStatusListener.DATA_LOADER_IMAGE_NOT_READY: {
- mDataLoaderFinished = true;
+ synchronized (mLock) {
+ mDataLoaderFinished = true;
+ }
dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
"Failed to prepare image.");
if (manualStartAndDestroy) {
@@ -2895,7 +2911,9 @@
break;
}
case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE:
- mDataLoaderFinished = true;
+ synchronized (mLock) {
+ mDataLoaderFinished = true;
+ }
dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
"DataLoader reported unrecoverable failure.");
break;
@@ -2919,7 +2937,11 @@
final IStorageHealthListener healthListener = new IStorageHealthListener.Stub() {
@Override
public void onHealthStatus(int storageId, int status) {
- if (mDestroyed || mDataLoaderFinished) {
+ final boolean isDestroyedOrDataLoaderFinished;
+ synchronized (mLock) {
+ isDestroyedOrDataLoaderFinished = mDestroyed || mDataLoaderFinished;
+ }
+ if (isDestroyedOrDataLoaderFinished) {
// App's installed.
switch (status) {
case IStorageHealthListener.HEALTH_STATUS_UNHEALTHY:
@@ -2941,7 +2963,9 @@
// fallthrough
case IStorageHealthListener.HEALTH_STATUS_UNHEALTHY:
// Even ADB installation can't wait for missing pages for too long.
- mDataLoaderFinished = true;
+ synchronized (mLock) {
+ mDataLoaderFinished = true;
+ }
dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
"Image is missing pages required for installation.");
break;
@@ -2984,13 +3008,17 @@
return EMPTY_CHILD_SESSION_ARRAY;
}
+ private boolean canBeAddedAsChild(int parentCandidate) {
+ synchronized (mLock) {
+ return (!hasParentSessionId() || mParentSessionId == parentCandidate)
+ && !mCommitted && !mDestroyed;
+ }
+ }
+
@Override
public void addChildSessionId(int childSessionId) {
final PackageInstallerSession childSession = mSessionProvider.getSession(childSessionId);
- if (childSession == null
- || (childSession.hasParentSessionId() && childSession.mParentSessionId != sessionId)
- || childSession.mCommitted
- || childSession.mDestroyed) {
+ if (childSession == null || !childSession.canBeAddedAsChild(sessionId)) {
throw new IllegalStateException("Unable to add child session " + childSessionId
+ " as it does not exist or is in an invalid state.");
}
@@ -3039,12 +3067,16 @@
}
boolean hasParentSessionId() {
- return mParentSessionId != SessionInfo.INVALID_ID;
+ synchronized (mLock) {
+ return mParentSessionId != SessionInfo.INVALID_ID;
+ }
}
@Override
public int getParentSessionId() {
- return mParentSessionId;
+ synchronized (mLock) {
+ return mParentSessionId;
+ }
}
private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
@@ -3134,27 +3166,37 @@
/** {@hide} */
boolean isStagedSessionReady() {
- return mStagedSessionReady;
+ synchronized (mLock) {
+ return mStagedSessionReady;
+ }
}
/** {@hide} */
boolean isStagedSessionApplied() {
- return mStagedSessionApplied;
+ synchronized (mLock) {
+ return mStagedSessionApplied;
+ }
}
/** {@hide} */
boolean isStagedSessionFailed() {
- return mStagedSessionFailed;
+ synchronized (mLock) {
+ return mStagedSessionFailed;
+ }
}
/** {@hide} */
@StagedSessionErrorCode int getStagedSessionErrorCode() {
- return mStagedSessionErrorCode;
+ synchronized (mLock) {
+ return mStagedSessionErrorCode;
+ }
}
/** {@hide} */
String getStagedSessionErrorMessage() {
- return mStagedSessionErrorMessage;
+ synchronized (mLock) {
+ return mStagedSessionErrorMessage;
+ }
}
private void destroyInternal() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index fd73d68..668f375 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1376,7 +1376,7 @@
long timeoutMs = -1;
while ((opt = getNextOption()) != null) {
switch (opt) {
- case "--wait":
+ case "--wait-for-staged-ready":
waitForStagedSessionReady = true;
// If there is only one remaining argument, then it represents the sessionId, we
// shouldn't try to parse it as timeoutMs.
@@ -2864,7 +2864,7 @@
}
sessionParams.installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK;
break;
- case "--wait":
+ case "--wait-for-staged-ready":
params.mWaitForStagedSessionReady = true;
try {
params.timeoutMs = Long.parseLong(peekNextArg());
@@ -3597,7 +3597,7 @@
pw.println(" [--preload] [--instant] [--full] [--dont-kill]");
pw.println(" [--enable-rollback]");
pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
- pw.println(" [--apex] [--wait TIMEOUT]");
+ pw.println(" [--apex] [--wait-for-staged-ready TIMEOUT]");
pw.println(" [PATH [SPLIT...]|-]");
pw.println(" Install an application. Must provide the apk data to install, either as");
pw.println(" file path(s) or '-' to read from stdin. Options are:");
@@ -3625,8 +3625,8 @@
pw.println(" 3=device setup, 4=user request");
pw.println(" --force-uuid: force install on to disk volume with given UUID");
pw.println(" --apex: install an .apex file, not an .apk");
- pw.println(" --wait: when performing staged install, wait TIMEOUT milliseconds");
- pw.println(" for pre-reboot verification to complete. If TIMEOUT is not");
+ pw.println(" --wait-for-staged-ready: when performing staged install, wait TIMEOUT");
+ pw.println(" ms for pre-reboot verification to complete. If TIMEOUT is not");
pw.println(" specified it will wait for " + DEFAULT_WAIT_MS + " milliseconds.");
pw.println("");
pw.println(" install-existing [--user USER_ID|all|current]");
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 018a1da..710185d 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -395,6 +395,14 @@
return false;
}
+ /**
+ * Returns true if the window has a letterbox and any part of that letterbox overlaps with
+ * the given {@code rect}.
+ */
+ default boolean isLetterboxedOverlappingWith(Rect rect) {
+ return false;
+ }
+
/** @return the current windowing mode of this window. */
int getWindowingMode();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 4c4680b..7b06759 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -44,8 +44,8 @@
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.hardware.power.Boost;
import android.hardware.power.Mode;
+import android.hardware.power.V1_0.PowerHint;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
@@ -203,6 +203,9 @@
// How long a partial wake lock must be held until we consider it a long wake lock.
static final long MIN_LONG_WAKE_CHECK_INTERVAL = 60*1000;
+ // Power features defined in hardware/libhardware/include/hardware/power.h.
+ private static final int POWER_FEATURE_DOUBLE_TAP_TO_WAKE = 1;
+
// Default setting for double tap to wake.
private static final int DEFAULT_DOUBLE_TAP_TO_WAKE = 0;
@@ -322,7 +325,7 @@
private long mLastUserActivityTime;
private long mLastUserActivityTimeNoChangeLights;
- // Timestamp of last time power boost interaction was sent.
+ // Timestamp of last interactive power hint.
private long mLastInteractivePowerHintTime;
// Timestamp of the last screen brightness boost.
@@ -716,11 +719,21 @@
PowerManagerService.nativeReleaseSuspendBlocker(name);
}
+ /** Wrapper for PowerManager.nativeSetInteractive */
+ public void nativeSetInteractive(boolean enable) {
+ PowerManagerService.nativeSetInteractive(enable);
+ }
+
/** Wrapper for PowerManager.nativeSetAutoSuspend */
public void nativeSetAutoSuspend(boolean enable) {
PowerManagerService.nativeSetAutoSuspend(enable);
}
+ /** Wrapper for PowerManager.nativeSendPowerHint */
+ public void nativeSendPowerHint(int hintId, int data) {
+ PowerManagerService.nativeSendPowerHint(hintId, data);
+ }
+
/** Wrapper for PowerManager.nativeSetPowerBoost */
public void nativeSetPowerBoost(int boost, int durationMs) {
PowerManagerService.nativeSetPowerBoost(boost, durationMs);
@@ -731,6 +744,11 @@
return PowerManagerService.nativeSetPowerMode(mode, enabled);
}
+ /** Wrapper for PowerManager.nativeSetFeature */
+ public void nativeSetFeature(int featureId, int data) {
+ PowerManagerService.nativeSetFeature(featureId, data);
+ }
+
/** Wrapper for PowerManager.nativeForceSuspend */
public boolean nativeForceSuspend() {
return PowerManagerService.nativeForceSuspend();
@@ -833,9 +851,12 @@
private native void nativeInit();
private static native void nativeAcquireSuspendBlocker(String name);
private static native void nativeReleaseSuspendBlocker(String name);
+ private static native void nativeSetInteractive(boolean enable);
private static native void nativeSetAutoSuspend(boolean enable);
+ private static native void nativeSendPowerHint(int hintId, int data);
private static native void nativeSetPowerBoost(int boost, int durationMs);
private static native boolean nativeSetPowerMode(int mode, boolean enabled);
+ private static native void nativeSetFeature(int featureId, int data);
private static native boolean nativeForceSuspend();
public PowerManagerService(Context context) {
@@ -979,8 +1000,8 @@
mNativeWrapper.nativeInit(this);
mNativeWrapper.nativeSetAutoSuspend(false);
- mNativeWrapper.nativeSetPowerMode(Mode.INTERACTIVE, true);
- mNativeWrapper.nativeSetPowerMode(Mode.DOUBLE_TAP_TO_WAKE, false);
+ mNativeWrapper.nativeSetInteractive(true);
+ mNativeWrapper.nativeSetFeature(POWER_FEATURE_DOUBLE_TAP_TO_WAKE, 0);
mInjector.invalidateIsInteractiveCaches();
}
}
@@ -1231,7 +1252,8 @@
UserHandle.USER_CURRENT) != 0;
if (doubleTapWakeEnabled != mDoubleTapWakeEnabled) {
mDoubleTapWakeEnabled = doubleTapWakeEnabled;
- mNativeWrapper.nativeSetPowerMode(Mode.DOUBLE_TAP_TO_WAKE, mDoubleTapWakeEnabled);
+ mNativeWrapper.nativeSetFeature(
+ POWER_FEATURE_DOUBLE_TAP_TO_WAKE, mDoubleTapWakeEnabled ? 1 : 0);
}
}
@@ -1590,7 +1612,7 @@
Trace.traceBegin(Trace.TRACE_TAG_POWER, "userActivity");
try {
if (eventTime > mLastInteractivePowerHintTime) {
- setPowerBoostInternal(Boost.INTERACTION, 0);
+ powerHintInternal(PowerHint.INTERACTION, 0);
mLastInteractivePowerHintTime = eventTime;
}
@@ -3149,7 +3171,7 @@
mHalInteractiveModeEnabled = enable;
Trace.traceBegin(Trace.TRACE_TAG_POWER, "setHalInteractive(" + enable + ")");
try {
- mNativeWrapper.nativeSetPowerMode(Mode.INTERACTIVE, enable);
+ mNativeWrapper.nativeSetInteractive(enable);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
@@ -3623,6 +3645,19 @@
mIsVrModeEnabled = enabled;
}
+ private void powerHintInternal(int hintId, int data) {
+ // Maybe filter the event.
+ switch (hintId) {
+ case PowerHint.LAUNCH: // 1: activate launch boost 0: deactivate.
+ if (data == 1 && mBatterySaverController.isLaunchBoostDisabled()) {
+ return;
+ }
+ break;
+ }
+
+ mNativeWrapper.nativeSendPowerHint(hintId, data);
+ }
+
private void setPowerBoostInternal(int boost, int durationMs) {
// Maybe filter the event.
mNativeWrapper.nativeSetPowerBoost(boost, durationMs);
@@ -4369,7 +4404,7 @@
private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@Override
public void onVrStateChanged(boolean enabled) {
- setPowerModeInternal(Mode.VR, enabled);
+ powerHintInternal(PowerHint.VR_MODE, enabled ? 1 : 0);
synchronized (mLock) {
if (mIsVrModeEnabled != enabled) {
@@ -4680,6 +4715,16 @@
}
@Override // Binder call
+ public void powerHint(int hintId, int data) {
+ if (!mSystemReady) {
+ // Service not ready yet, so who the heck cares about power hints, bah.
+ return;
+ }
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+ powerHintInternal(hintId, data);
+ }
+
+ @Override // Binder call
public void setPowerBoost(int boost, int durationMs) {
if (!mSystemReady) {
// Service not ready yet, so who the heck cares about power hints, bah.
@@ -5514,6 +5559,11 @@
}
@Override
+ public void powerHint(int hintId, int data) {
+ powerHintInternal(hintId, data);
+ }
+
+ @Override
public void setPowerBoost(int boost, int durationMs) {
setPowerBoostInternal(boost, durationMs);
}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index 6ff069b..2a4a69d 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -23,7 +23,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManagerInternal;
-import android.hardware.power.Mode;
+import android.hardware.power.V1_0.PowerHint;
import android.os.BatteryManager;
import android.os.BatterySaverPolicyConfig;
import android.os.Handler;
@@ -474,7 +474,7 @@
final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
if (pmi != null) {
- pmi.setPowerMode(Mode.LOW_POWER, isEnabled());
+ pmi.powerHint(PowerHint.LOW_POWER, isEnabled() ? 1 : 0);
}
updateBatterySavingStats();
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
index 72f8027..761858c 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
@@ -192,10 +192,7 @@
}
private static RuntimeException handleException(RuntimeException e) {
- // TODO(b/160169016): There is currently no other way to distinguish dead object from other
- // exceptions.
- if (e.getCause() instanceof RemoteException &&
- e.getCause().getMessage().equals("HwBinder Error: (-32)")) {
+ if (e.getCause() instanceof DeadObjectException) {
// Server is dead, no need to reboot.
Log.e(TAG, "HAL died");
} else {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a87fe61..4e355f0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1394,10 +1394,11 @@
}
/**
- * @see Letterbox#notIntersectsOrFullyContains(Rect)
+ * @return {@code true} if there is a letterbox and any part of that letterbox overlaps with
+ * the given {@code rect}.
*/
- boolean letterboxNotIntersectsOrFullyContains(Rect rect) {
- return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect);
+ boolean isLetterboxOverlappingWith(Rect rect) {
+ return mLetterbox != null && mLetterbox.isOverlappingWith(rect);
}
static class Token extends IApplicationToken.Stub {
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index ea9aab7..686e016 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -2793,7 +2793,6 @@
Task task = child.asTask();
try {
-
if (task != null) {
task.setForceShowForAllUsers(showForAllUsers);
}
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
index 26e0790..123fb6c 100644
--- a/services/core/java/com/android/server/wm/BarController.java
+++ b/services/core/java/com/android/server/wm/BarController.java
@@ -175,7 +175,7 @@
}
final Rect rotatedContentFrame = win.mToken.getFixedRotationBarContentFrame(mWindowType);
final Rect contentFrame = rotatedContentFrame != null ? rotatedContentFrame : mContentFrame;
- return win.letterboxNotIntersectsOrFullyContains(contentFrame);
+ return !win.isLetterboxedOverlappingWith(contentFrame);
}
boolean setBarShowingLw(final boolean show) {
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 201473e..fbb2fcb 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -109,6 +109,66 @@
}
@Override
+ void positionChildAt(int position, T child, boolean includingParents) {
+ if (child.asDisplayArea() == null) {
+ // Reposition other window containers as normal.
+ super.positionChildAt(position, child, includingParents);
+ return;
+ }
+
+ final int targetPosition = findPositionForChildDisplayArea(position, child.asDisplayArea());
+ super.positionChildAt(targetPosition, child, false /* includingParents */);
+
+ final WindowContainer parent = getParent();
+ if (includingParents && parent != null
+ && (position == POSITION_TOP || position == POSITION_BOTTOM)) {
+ parent.positionChildAt(position, this /* child */, true /* includingParents */);
+ }
+ }
+
+ /**
+ * When a {@link DisplayArea} is repositioned, it should only be moved among its siblings of the
+ * same {@link Type}.
+ * For example, when a {@link DisplayArea} of {@link Type#ANY} is repositioned, it shouldn't be
+ * moved above any {@link Type#ABOVE_TASKS} siblings, or below any {@link Type#BELOW_TASKS}
+ * siblings.
+ */
+ private int findPositionForChildDisplayArea(int requestPosition, DisplayArea child) {
+ if (child.getParent() != this) {
+ throw new IllegalArgumentException("positionChildAt: container=" + child.getName()
+ + " is not a child of container=" + getName()
+ + " current parent=" + child.getParent());
+ }
+
+ // The max possible position we can insert the child at.
+ int maxPosition = findMaxPositionForChildDisplayArea(child);
+ // The min possible position we can insert the child at.
+ int minPosition = findMinPositionForChildDisplayArea(child);
+
+ return Math.max(Math.min(requestPosition, maxPosition), minPosition);
+ }
+
+ private int findMaxPositionForChildDisplayArea(DisplayArea child) {
+ final Type childType = Type.typeOf(child);
+ for (int i = mChildren.size() - 1; i > 0; i--) {
+ if (Type.typeOf(getChildAt(i)) == childType) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ private int findMinPositionForChildDisplayArea(DisplayArea child) {
+ final Type childType = Type.typeOf(child);
+ for (int i = 0; i < mChildren.size(); i++) {
+ if (Type.typeOf(getChildAt(i)) == childType) {
+ return i;
+ }
+ }
+ return mChildren.size() - 1;
+ }
+
+ @Override
boolean needsZBoost() {
// Z Boost should only happen at or below the ActivityStack level.
return false;
@@ -423,7 +483,7 @@
}
static Type typeOf(WindowContainer c) {
- if (c instanceof DisplayArea) {
+ if (c.asDisplayArea() != null) {
return ((DisplayArea) c).mType;
} else if (c instanceof WindowToken && !(c instanceof ActivityRecord)) {
return typeOf((WindowToken) c);
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index a161aca..59c3204 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -63,6 +63,11 @@
*/
public abstract List<DisplayArea<? extends WindowContainer>> getDisplayAreas(int featureId);
+ /**
+ * @return the default/fallback {@link TaskDisplayArea} on the display.
+ */
+ public abstract TaskDisplayArea getDefaultTaskDisplayArea();
+
/** Provider for platform-default display area policy. */
static final class DefaultProvider implements DisplayAreaPolicy.Provider {
@Override
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
index 465d089..681b21c 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -70,7 +70,7 @@
*
* </pre>
*
- * // TODO(b/157683117): document more complex scenarios where we need multiple areas per feature.
+ * // TODO(b/158713595): document more complex scenarios where we need multiple areas per feature.
*/
class DisplayAreaPolicyBuilder {
@Nullable private HierarchyBuilder mRootHierarchyBuilder;
@@ -123,8 +123,14 @@
}
containsImeContainer = containsImeContainer || hierarchyBuilder.mImeContainer != null;
- containsDefaultTda = containsDefaultTda
- || containsDefaultTaskDisplayArea(hierarchyBuilder);
+ if (containsDefaultTda) {
+ if (containsDefaultTaskDisplayArea(hierarchyBuilder)) {
+ throw new IllegalStateException("Only one TaskDisplayArea can have the feature "
+ + "of FEATURE_DEFAULT_TASK_CONTAINER");
+ }
+ } else {
+ containsDefaultTda = containsDefaultTaskDisplayArea(hierarchyBuilder);
+ }
}
if (!containsImeContainer) {
@@ -506,6 +512,7 @@
static class Result extends DisplayAreaPolicy {
final List<RootDisplayArea> mDisplayAreaGroupRoots;
final BiFunction<WindowToken, Bundle, RootDisplayArea> mSelectRootForWindowFunc;
+ private final TaskDisplayArea mDefaultTaskDisplayArea;
Result(WindowManagerService wmService, RootDisplayArea root,
List<RootDisplayArea> displayAreaGroupRoots,
@@ -518,6 +525,16 @@
// not specified.
? (window, options) -> mRoot
: selectRootForWindowFunc;
+
+ // Cache the default TaskDisplayArea for quick access.
+ mDefaultTaskDisplayArea = mRoot.getItemFromTaskDisplayAreas(taskDisplayArea ->
+ taskDisplayArea.mFeatureId == FEATURE_DEFAULT_TASK_CONTAINER
+ ? taskDisplayArea
+ : null);
+ if (mDefaultTaskDisplayArea == null) {
+ throw new IllegalStateException(
+ "No display area with FEATURE_DEFAULT_TASK_CONTAINER");
+ }
}
@Override
@@ -561,6 +578,11 @@
}
}
}
+
+ @Override
+ public TaskDisplayArea getDefaultTaskDisplayArea() {
+ return mDefaultTaskDisplayArea;
+ }
}
static class PendingArea {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0a3b884..de91af7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2512,8 +2512,7 @@
* or for cases when multi-instance is not supported yet (like Split-screen, PiP or Recents).
*/
TaskDisplayArea getDefaultTaskDisplayArea() {
- return getItemFromTaskDisplayAreas(taskDisplayArea -> taskDisplayArea,
- false /* traverseTopToBottom */);
+ return mDisplayAreaPolicy.getDefaultTaskDisplayArea();
}
void positionDisplayAt(int position, boolean includingParents) {
@@ -2545,9 +2544,11 @@
* Find the task whose outside touch area (for resizing) (x, y) falls within.
* Returns null if the touch doesn't fall into a resizing area.
*/
+ @Nullable
Task findTaskForResizePoint(int x, int y) {
final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
- return mTmpTaskForResizePointSearchResult.process(getDefaultTaskDisplayArea(), x, y, delta);
+ return getItemFromTaskDisplayAreas(taskDisplayArea ->
+ mTmpTaskForResizePointSearchResult.process(taskDisplayArea, x, y, delta));
}
void updateTouchExcludeRegion() {
@@ -2615,7 +2616,7 @@
// to exclude the docked stack from touch so we need the entire screen area and not just a
// small portion which the home stack currently is resized to.
if (task.isActivityTypeHome() && task.isVisible() && task.isResizeable()) {
- mDisplayContent.getBounds(mTmpRect);
+ task.getDisplayArea().getBounds(mTmpRect);
} else {
task.getDimBounds(mTmpRect);
}
@@ -3426,12 +3427,13 @@
}
private boolean isImeControlledByApp() {
- return mInputMethodTarget != null && !WindowConfiguration.isSplitScreenWindowingMode(
- mInputMethodTarget.getWindowingMode());
+ return mInputMethodInputTarget != null && !WindowConfiguration.isSplitScreenWindowingMode(
+ mInputMethodInputTarget.getWindowingMode());
}
boolean isImeAttachedToApp() {
return isImeControlledByApp()
+ && mInputMethodTarget != null
&& mInputMethodTarget.mActivityRecord != null
&& mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
// An activity with override bounds should be letterboxed inside its parent bounds,
@@ -3457,9 +3459,9 @@
InsetsControlTarget getImeFallback() {
// host is in non-default display that doesn't support system decor, default to
// default display's StatusBar to control IME (when available), else let system control it.
- WindowState statusBar =
- mWmService.getDefaultDisplayContentLocked().getDisplayPolicy().getStatusBar();
- return statusBar != null ? statusBar : mRemoteInsetsControlTarget;
+ final DisplayContent defaultDc = mWmService.getDefaultDisplayContentLocked();
+ WindowState statusBar = defaultDc.getDisplayPolicy().getStatusBar();
+ return statusBar != null ? statusBar : defaultDc.mRemoteInsetsControlTarget;
}
boolean canShowIme() {
@@ -3519,7 +3521,10 @@
*/
@VisibleForTesting
InsetsControlTarget computeImeControlTarget() {
- if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null) {
+ if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null
+ || (mInputMethodInputTarget != null
+ && getImeHostOrFallback(mInputMethodInputTarget.getWindow())
+ == mRemoteInsetsControlTarget)) {
return mRemoteInsetsControlTarget;
} else {
// Now, a special case -- if the last target's window is in the process of exiting, but
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index d2f5d2d..b4ead8e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -139,7 +139,7 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
-import android.hardware.power.Boost;
+import android.hardware.power.V1_0.PowerHint;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -544,8 +544,8 @@
@Override
public void onFling(int duration) {
if (mService.mPowerManagerInternal != null) {
- mService.mPowerManagerInternal.setPowerBoost(
- Boost.INTERACTION, duration);
+ mService.mPowerManagerInternal.powerHint(
+ PowerHint.INTERACTION, duration);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index c63128c..0747e24 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -42,7 +42,7 @@
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
-import android.hardware.power.Boost;
+import android.hardware.power.V1_0.PowerHint;
import android.net.Uri;
import android.os.Handler;
import android.os.RemoteException;
@@ -1473,7 +1473,7 @@
@Override
public void run() {
// Send interaction power boost to improve redraw performance.
- mService.mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
+ mService.mPowerManagerInternal.powerHint(PowerHint.INTERACTION, 0);
if (isRotationChoicePossible(mCurrentAppOrientation)) {
final boolean isValid = isValidRotationChoice(mRotation);
sendProposedRotationChangeToStatusBarInternal(mRotation, isValid);
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 99ee5e1..e7fbc334 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -32,7 +32,7 @@
*/
class ImeInsetsSourceProvider extends InsetsSourceProvider {
- private WindowState mImeTargetFromIme;
+ private InsetsControlTarget mImeTargetFromIme;
private Runnable mShowImeRunner;
private boolean mIsImeLayoutDrawn;
@@ -47,10 +47,12 @@
*
* @param imeTarget imeTarget on which IME request is coming from.
*/
- void scheduleShowImePostLayout(WindowState imeTarget) {
+ void scheduleShowImePostLayout(InsetsControlTarget imeTarget) {
boolean targetChanged = mImeTargetFromIme != imeTarget
&& mImeTargetFromIme != null && imeTarget != null && mShowImeRunner != null
- && mImeTargetFromIme.mActivityRecord == imeTarget.mActivityRecord;
+ && imeTarget.getWindow() != null && mImeTargetFromIme.getWindow() != null
+ && mImeTargetFromIme.getWindow().mActivityRecord
+ == imeTarget.getWindow().mActivityRecord;
mImeTargetFromIme = imeTarget;
if (targetChanged) {
// target changed, check if new target can show IME.
@@ -62,7 +64,8 @@
return;
}
- ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeTargetFromIme.getName());
+ ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeTargetFromIme.getWindow() == null
+ ? mImeTargetFromIme : mImeTargetFromIme.getWindow().getName());
mShowImeRunner = () -> {
ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
// Target should still be the same.
@@ -127,13 +130,17 @@
return false;
}
ProtoLog.d(WM_DEBUG_IME, "dcTarget: %s mImeTargetFromIme: %s",
- dcTarget.getName(), mImeTargetFromIme.getName());
+ dcTarget.getName(), mImeTargetFromIme.getWindow() == null
+ ? mImeTargetFromIme : mImeTargetFromIme.getWindow().getName());
return (!dcTarget.isClosing() && mImeTargetFromIme == dcTarget)
- || (mImeTargetFromIme != null && dcTarget.getParentWindow() == mImeTargetFromIme
- && dcTarget.mSubLayer > mImeTargetFromIme.mSubLayer)
+ || (mImeTargetFromIme != null && mImeTargetFromIme.getWindow() != null
+ && dcTarget.getParentWindow() == mImeTargetFromIme
+ && dcTarget.mSubLayer > mImeTargetFromIme.getWindow().mSubLayer)
|| mImeTargetFromIme == mDisplayContent.getImeFallback()
- || (!mImeTargetFromIme.isClosing() && controlTarget == mImeTargetFromIme);
+ || controlTarget == mImeTargetFromIme
+ && (mImeTargetFromIme.getWindow() == null
+ || !mImeTargetFromIme.getWindow().isClosing());
}
@Override
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 5d1c85d..a685886 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -77,10 +77,10 @@
mOuter.set(outer);
mInner.set(inner);
- mTop.layout(outer.left, outer.top, outer.right, inner.top, surfaceOrigin);
- mLeft.layout(outer.left, outer.top, inner.left, outer.bottom, surfaceOrigin);
- mBottom.layout(outer.left, inner.bottom, outer.right, outer.bottom, surfaceOrigin);
- mRight.layout(inner.right, outer.top, outer.right, outer.bottom, surfaceOrigin);
+ mTop.layout(outer.left, outer.top, inner.right, inner.top, surfaceOrigin);
+ mLeft.layout(outer.left, inner.top, inner.left, outer.bottom, surfaceOrigin);
+ mBottom.layout(inner.left, inner.bottom, outer.right, outer.bottom, surfaceOrigin);
+ mRight.layout(inner.right, outer.top, outer.right, inner.bottom, surfaceOrigin);
}
@@ -101,29 +101,17 @@
}
/**
- * Returns {@code true} if the letterbox does not overlap with the bar, or the letterbox can
- * fully cover the window frame.
- *
- * @param rect The area of the window frame.
+ * Returns true if any part of the letterbox overlaps with the given {@code rect}.
*/
- boolean notIntersectsOrFullyContains(Rect rect) {
- int emptyCount = 0;
- int noOverlappingCount = 0;
+ public boolean isOverlappingWith(Rect rect) {
for (LetterboxSurface surface : mSurfaces) {
- final Rect surfaceRect = surface.mLayoutFrameGlobal;
- if (surfaceRect.isEmpty()) {
- // empty letterbox
- emptyCount++;
- } else if (!Rect.intersects(surfaceRect, rect)) {
- // no overlapping
- noOverlappingCount++;
- } else if (surfaceRect.contains(rect)) {
- // overlapping and covered
+ if (surface.isOverlappingWith(rect)) {
return true;
}
}
- return (emptyCount + noOverlappingCount) == mSurfaces.length;
+ return false;
}
+
/**
* Hides the letterbox.
*
@@ -298,6 +286,17 @@
return Math.max(0, mLayoutFrameGlobal.height());
}
+ /**
+ * Returns if the given {@code rect} overlaps with this letterbox piece.
+ * @param rect the area to check for overlap in global coordinates
+ */
+ public boolean isOverlappingWith(Rect rect) {
+ if (mLayoutFrameGlobal.isEmpty()) {
+ return false;
+ }
+ return Rect.intersects(rect, mLayoutFrameGlobal);
+ }
+
public void applySurfaceChanges(SurfaceControl.Transaction t) {
if (mSurfaceFrameRelative.equals(mLayoutFrameRelative)) {
// Nothing changed.
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 40f8fab..ab21519 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -111,7 +111,7 @@
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
-import android.hardware.power.Mode;
+import android.hardware.power.V1_0.PowerHint;
import android.net.Uri;
import android.os.Binder;
import android.os.Debug;
@@ -950,9 +950,9 @@
if (mSustainedPerformanceModeCurrent != mSustainedPerformanceModeEnabled) {
mSustainedPerformanceModeEnabled = mSustainedPerformanceModeCurrent;
- mWmService.mPowerManagerInternal.setPowerMode(
- Mode.SUSTAINED_PERFORMANCE,
- mSustainedPerformanceModeEnabled);
+ mWmService.mPowerManagerInternal.powerHint(
+ PowerHint.SUSTAINED_PERFORMANCE,
+ mSustainedPerformanceModeEnabled ? 1 : 0);
}
if (mUpdateRotation) {
@@ -3478,7 +3478,7 @@
}
if (sendPowerModeLaunch && mService.mPowerManagerInternal != null) {
- mService.mPowerManagerInternal.setPowerMode(Mode.LAUNCH, true);
+ mService.mPowerManagerInternal.powerHint(PowerHint.LAUNCH, 1);
mPowerModeLaunchStarted = true;
}
}
@@ -3486,7 +3486,7 @@
void endPowerModeLaunchIfNeeded() {
// Trigger launch power mode off if activity is launched
if (mPowerModeLaunchStarted && mService.mPowerManagerInternal != null) {
- mService.mPowerManagerInternal.setPowerMode(Mode.LAUNCH, false);
+ mService.mPowerManagerInternal.powerHint(PowerHint.LAUNCH, 0);
mPowerModeLaunchStarted = false;
}
}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 34d084a..837f1b5 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -26,7 +26,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
-import android.hardware.power.Boost;
+import android.hardware.power.V1_0.PowerHint;
import android.os.Handler;
import android.os.PowerManagerInternal;
import android.util.ArrayMap;
@@ -246,7 +246,7 @@
synchronized (mLock) {
startPendingAnimationsLocked();
}
- mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
+ mPowerManagerInternal.powerHint(PowerHint.INTERACTION, 0);
}
private void scheduleApplyTransaction() {
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 8bab106..aba5b99 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -331,7 +331,7 @@
@Override
void positionChildAt(int position, ActivityStack child, boolean includingParents) {
final boolean moveToTop = position >= getChildCount() - 1;
- final boolean moveToBottom = (position == POSITION_BOTTOM || position == 0);
+ final boolean moveToBottom = position <= 0;
if (child.getWindowConfiguration().isAlwaysOnTop() && !moveToTop) {
// This stack is always-on-top, override the default behavior.
@@ -351,12 +351,9 @@
final int targetPosition = findPositionForStack(position, child, false /* adding */);
super.positionChildAt(targetPosition, child, false /* includingParents */);
- if (includingParents && (moveToTop || moveToBottom)) {
- // The DisplayContent children do not re-order, but we still want to move the
- // display of this stack container because the intention of positioning is to have
- // higher z-order to gain focus.
- mDisplayContent.positionDisplayAt(moveToTop ? POSITION_TOP : POSITION_BOTTOM,
- true /* includingParents */);
+ if (includingParents && getParent() != null && (moveToTop || moveToBottom)) {
+ getParent().positionChildAt(moveToTop ? POSITION_TOP : POSITION_BOTTOM,
+ this /* child */, true /* includingParents */);
}
child.updateTaskMovement(moveToTop);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 7b92c28..f1e965b 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -685,7 +685,6 @@
*/
@CallSuper
void positionChildAt(int position, E child, boolean includingParents) {
-
if (child.getParent() != this) {
throw new IllegalArgumentException("positionChildAt: container=" + child.getName()
+ " is not a child of container=" + getName()
@@ -995,11 +994,13 @@
* changes (eg. a transition animation might play out first).
*/
void onChildVisibilityRequested(boolean visible) {
- // If we are changing visibility, then a snapshot isn't necessary and we are no-longer
+ // If we are losing visibility, then a snapshot isn't necessary and we are no-longer
// part of a change transition.
- mSurfaceFreezer.unfreeze(getSyncTransaction());
- if (mDisplayContent != null) {
- mDisplayContent.mChangingContainers.remove(this);
+ if (!visible) {
+ mSurfaceFreezer.unfreeze(getSyncTransaction());
+ if (mDisplayContent != null) {
+ mDisplayContent.mChangingContainers.remove(this);
+ }
}
WindowContainer parent = getParent();
if (parent != null) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 85972bf..4df48dc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7599,13 +7599,14 @@
if (imeTarget == null) {
return;
}
- imeTarget = imeTarget.getImeControlTarget().getWindow();
+ final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget();
+ imeTarget = controlTarget.getWindow();
// If InsetsControlTarget doesn't have a window, its using remoteControlTarget which
// is controlled by default display
final DisplayContent dc = imeTarget != null
? imeTarget.getDisplayContent() : getDefaultDisplayContentLocked();
dc.getInsetsStateController().getImeSourceProvider()
- .scheduleShowImePostLayout(imeTarget);
+ .scheduleShowImePostLayout(controlTarget);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 10bcbeb..24ad853 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -324,12 +324,13 @@
(task.isRootTask() && !task.mCreatedByOrganizer)
|| task.getParent().asTask().mCreatedByOrganizer;
if (isNonOrganizedRootableTask) {
- Task newParent = hop.getNewParent() == null ? null
- : WindowContainer.fromBinder(hop.getNewParent()).asTask();
+ WindowContainer newParent = hop.getNewParent() == null
+ ? dc.getDefaultTaskDisplayArea()
+ : WindowContainer.fromBinder(hop.getNewParent());
if (task.getParent() != newParent) {
- if (newParent == null) {
- // Re-parent task to display as a root task.
- as.reparent(dc.getDefaultTaskDisplayArea(), hop.getToTop());
+ if (newParent instanceof TaskDisplayArea) {
+ // For now, reparenting to displayarea is different from other reparents...
+ as.reparent((TaskDisplayArea) newParent, hop.getToTop());
} else if (newParent.inMultiWindowMode() && !task.isResizeable()
&& task.isLeafTask()) {
Slog.w(TAG, "Can't support task that doesn't support multi-window mode in"
@@ -341,8 +342,9 @@
false /*moveParents*/, "sanitizeAndApplyHierarchyOp");
}
} else {
- final ActivityStack rootTask =
- (ActivityStack) (newParent != null ? newParent : task.getRootTask());
+ final ActivityStack rootTask = (ActivityStack) (
+ (newParent != null && !(newParent instanceof TaskDisplayArea))
+ ? newParent : task.getRootTask());
if (hop.getToTop()) {
as.getDisplayArea().positionStackAtTop(rootTask,
false /* includingParents */);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 86bc0a2..27c4bf4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3819,12 +3819,9 @@
return mActivityRecord.getBounds().equals(mTmpRect);
}
- /**
- * @see Letterbox#notIntersectsOrFullyContains(Rect)
- */
- boolean letterboxNotIntersectsOrFullyContains(Rect rect) {
- return mActivityRecord == null
- || mActivityRecord.letterboxNotIntersectsOrFullyContains(rect);
+ @Override
+ public boolean isLetterboxedOverlappingWith(Rect rect) {
+ return mActivityRecord != null && mActivityRecord.isLetterboxOverlappingWith(rect);
}
boolean isDragResizeChanged() {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 5f1895a..0e1b2f2 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -14,7 +14,6 @@
],
srcs: [
- ":lib_alarmManagerService_native",
"BroadcastRadio/JavaRef.cpp",
"BroadcastRadio/NativeCallbackThread.cpp",
"BroadcastRadio/BroadcastRadioService.cpp",
@@ -72,17 +71,6 @@
header_libs: [
"bionic_libc_platform_headers",
],
-
- product_variables: {
- arc: {
- exclude_srcs: [
- "com_android_server_alarm_AlarmManagerService.cpp",
- ],
- srcs: [
- ":arctimersrcs",
- ],
- }
- }
}
cc_defaults {
@@ -198,10 +186,3 @@
"com_android_server_net_NetworkStatsFactory.cpp",
],
}
-
-filegroup {
- name: "lib_alarmManagerService_native",
- srcs: [
- "com_android_server_alarm_AlarmManagerService.cpp",
- ],
-}
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index d11c4bc..6140531 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -29,6 +29,7 @@
#include <nativehelper/ScopedUtfChars.h>
#include <powermanager/PowerHalController.h>
+#include <powermanager/PowerHalLoader.h>
#include <limits.h>
@@ -47,13 +48,17 @@
#include "com_android_server_power_PowerManagerService.h"
+using android::String8;
+using android::hardware::Return;
+using android::hardware::Void;
using android::hardware::power::Boost;
using android::hardware::power::Mode;
-using android::String8;
+using android::hardware::power::V1_0::Feature;
+using android::hardware::power::V1_0::PowerHint;
+using android::system::suspend::ISuspendControlService;
using android::system::suspend::V1_0::ISystemSuspend;
using android::system::suspend::V1_0::IWakeLock;
using android::system::suspend::V1_0::WakeLockType;
-using android::system::suspend::ISuspendControlService;
using IPowerV1_1 = android::hardware::power::V1_1::IPower;
using IPowerV1_0 = android::hardware::power::V1_0::IPower;
using IPowerAidl = android::hardware::power::IPower;
@@ -69,7 +74,18 @@
// ----------------------------------------------------------------------------
static jobject gPowerManagerServiceObj;
-static power::PowerHalController gPowerHalController;
+static sp<IPowerV1_0> gPowerHalHidlV1_0_ = nullptr;
+static sp<IPowerV1_1> gPowerHalHidlV1_1_ = nullptr;
+static sp<IPowerAidl> gPowerHalAidl_ = nullptr;
+static std::mutex gPowerHalMutex;
+
+enum class HalVersion {
+ NONE,
+ HIDL_1_0,
+ HIDL_1_1,
+ AIDL,
+};
+
static nsecs_t gLastEventTime[USER_ACTIVITY_EVENT_LAST + 1];
// Throttling interval for user activity calls.
@@ -87,19 +103,208 @@
return false;
}
+// Check validity of current handle to the power HAL service, and connect to it if necessary.
+// The caller must be holding gPowerHalMutex.
+static HalVersion connectPowerHalLocked() {
+ static bool gPowerHalHidlExists = true;
+ static bool gPowerHalAidlExists = true;
+ if (!gPowerHalHidlExists && !gPowerHalAidlExists) {
+ return HalVersion::NONE;
+ }
+ if (gPowerHalAidlExists) {
+ if (!gPowerHalAidl_) {
+ gPowerHalAidl_ = waitForVintfService<IPowerAidl>();
+ }
+ if (gPowerHalAidl_) {
+ ALOGV("Successfully connected to Power HAL AIDL service.");
+ return HalVersion::AIDL;
+ } else {
+ gPowerHalAidlExists = false;
+ }
+ }
+ if (gPowerHalHidlExists && gPowerHalHidlV1_0_ == nullptr) {
+ gPowerHalHidlV1_0_ = IPowerV1_0::getService();
+ if (gPowerHalHidlV1_0_) {
+ ALOGV("Successfully connected to Power HAL HIDL 1.0 service.");
+ // Try cast to powerHAL HIDL V1_1
+ gPowerHalHidlV1_1_ = IPowerV1_1::castFrom(gPowerHalHidlV1_0_);
+ if (gPowerHalHidlV1_1_) {
+ ALOGV("Successfully connected to Power HAL HIDL 1.1 service.");
+ }
+ } else {
+ ALOGV("Couldn't load power HAL HIDL service");
+ gPowerHalHidlExists = false;
+ return HalVersion::NONE;
+ }
+ }
+ if (gPowerHalHidlV1_1_) {
+ return HalVersion::HIDL_1_1;
+ } else if (gPowerHalHidlV1_0_) {
+ return HalVersion::HIDL_1_0;
+ }
+ return HalVersion::NONE;
+}
+
+// Check if a call to a power HAL function failed; if so, log the failure and invalidate the
+// current handle to the power HAL service.
+bool processPowerHalReturn(bool isOk, const char* functionName) {
+ if (!isOk) {
+ ALOGE("%s() failed: power HAL service not available.", functionName);
+ gPowerHalMutex.lock();
+ gPowerHalHidlV1_0_ = nullptr;
+ gPowerHalHidlV1_1_ = nullptr;
+ gPowerHalAidl_ = nullptr;
+ gPowerHalMutex.unlock();
+ }
+ return isOk;
+}
+
+enum class HalSupport {
+ UNKNOWN = 0,
+ ON,
+ OFF,
+};
+
+static void setPowerBoostWithHandle(sp<IPowerAidl> handle, Boost boost, int32_t durationMs) {
+ // Android framework only sends boost upto DISPLAY_UPDATE_IMMINENT.
+ // Need to increase the array size if more boost supported.
+ static std::array<std::atomic<HalSupport>,
+ static_cast<int32_t>(Boost::DISPLAY_UPDATE_IMMINENT) + 1>
+ boostSupportedArray = {HalSupport::UNKNOWN};
+
+ // Quick return if boost is not supported by HAL
+ if (boost > Boost::DISPLAY_UPDATE_IMMINENT ||
+ boostSupportedArray[static_cast<int32_t>(boost)] == HalSupport::OFF) {
+ ALOGV("Skipped setPowerBoost %s because HAL doesn't support it", toString(boost).c_str());
+ return;
+ }
+
+ if (boostSupportedArray[static_cast<int32_t>(boost)] == HalSupport::UNKNOWN) {
+ bool isSupported = false;
+ handle->isBoostSupported(boost, &isSupported);
+ boostSupportedArray[static_cast<int32_t>(boost)] =
+ isSupported ? HalSupport::ON : HalSupport::OFF;
+ if (!isSupported) {
+ ALOGV("Skipped setPowerBoost %s because HAL doesn't support it",
+ toString(boost).c_str());
+ return;
+ }
+ }
+
+ auto ret = handle->setBoost(boost, durationMs);
+ processPowerHalReturn(ret.isOk(), "setPowerBoost");
+}
+
static void setPowerBoost(Boost boost, int32_t durationMs) {
- gPowerHalController.setBoost(boost, durationMs);
- SurfaceComposerClient::notifyPowerBoost(static_cast<int32_t>(boost));
+ std::unique_lock<std::mutex> lock(gPowerHalMutex);
+ if (connectPowerHalLocked() != HalVersion::AIDL) {
+ ALOGV("Power HAL AIDL not available");
+ return;
+ }
+ sp<IPowerAidl> handle = gPowerHalAidl_;
+ lock.unlock();
+ setPowerBoostWithHandle(handle, boost, durationMs);
+}
+
+static bool setPowerModeWithHandle(sp<IPowerAidl> handle, Mode mode, bool enabled) {
+ // Android framework only sends mode upto DISPLAY_INACTIVE.
+ // Need to increase the array if more mode supported.
+ static std::array<std::atomic<HalSupport>, static_cast<int32_t>(Mode::DISPLAY_INACTIVE) + 1>
+ modeSupportedArray = {HalSupport::UNKNOWN};
+
+ // Quick return if mode is not supported by HAL
+ if (mode > Mode::DISPLAY_INACTIVE ||
+ modeSupportedArray[static_cast<int32_t>(mode)] == HalSupport::OFF) {
+ ALOGV("Skipped setPowerMode %s because HAL doesn't support it", toString(mode).c_str());
+ return false;
+ }
+
+ if (modeSupportedArray[static_cast<int32_t>(mode)] == HalSupport::UNKNOWN) {
+ bool isSupported = false;
+ handle->isModeSupported(mode, &isSupported);
+ modeSupportedArray[static_cast<int32_t>(mode)] =
+ isSupported ? HalSupport::ON : HalSupport::OFF;
+ if (!isSupported) {
+ ALOGV("Skipped setPowerMode %s because HAL doesn't support it", toString(mode).c_str());
+ return false;
+ }
+ }
+
+ auto ret = handle->setMode(mode, enabled);
+ processPowerHalReturn(ret.isOk(), "setPowerMode");
+ return ret.isOk();
}
static bool setPowerMode(Mode mode, bool enabled) {
- android::base::Timer t;
- auto result = gPowerHalController.setMode(mode, enabled);
- if (mode == Mode::INTERACTIVE && t.duration() > 20ms) {
- ALOGD("Excessive delay in setting interactive mode to %s while turning screen %s",
- enabled ? "true" : "false", enabled ? "on" : "off");
+ std::unique_lock<std::mutex> lock(gPowerHalMutex);
+ if (connectPowerHalLocked() != HalVersion::AIDL) {
+ ALOGV("Power HAL AIDL not available");
+ return false;
}
- return result == power::HalResult::SUCCESSFUL;
+ sp<IPowerAidl> handle = gPowerHalAidl_;
+ lock.unlock();
+ return setPowerModeWithHandle(handle, mode, enabled);
+}
+
+static void sendPowerHint(PowerHint hintId, uint32_t data) {
+ std::unique_lock<std::mutex> lock(gPowerHalMutex);
+ switch (connectPowerHalLocked()) {
+ case HalVersion::NONE:
+ return;
+ case HalVersion::HIDL_1_0: {
+ sp<IPowerV1_0> handle = gPowerHalHidlV1_0_;
+ lock.unlock();
+ auto ret = handle->powerHint(hintId, data);
+ processPowerHalReturn(ret.isOk(), "powerHint");
+ break;
+ }
+ case HalVersion::HIDL_1_1: {
+ sp<IPowerV1_1> handle = gPowerHalHidlV1_1_;
+ lock.unlock();
+ auto ret = handle->powerHintAsync(hintId, data);
+ processPowerHalReturn(ret.isOk(), "powerHintAsync");
+ break;
+ }
+ case HalVersion::AIDL: {
+ if (hintId == PowerHint::INTERACTION) {
+ sp<IPowerAidl> handle = gPowerHalAidl_;
+ lock.unlock();
+ setPowerBoostWithHandle(handle, Boost::INTERACTION, data);
+ break;
+ } else if (hintId == PowerHint::LAUNCH) {
+ sp<IPowerAidl> handle = gPowerHalAidl_;
+ lock.unlock();
+ setPowerModeWithHandle(handle, Mode::LAUNCH, static_cast<bool>(data));
+ break;
+ } else if (hintId == PowerHint::LOW_POWER) {
+ sp<IPowerAidl> handle = gPowerHalAidl_;
+ lock.unlock();
+ setPowerModeWithHandle(handle, Mode::LOW_POWER, static_cast<bool>(data));
+ break;
+ } else if (hintId == PowerHint::SUSTAINED_PERFORMANCE) {
+ sp<IPowerAidl> handle = gPowerHalAidl_;
+ lock.unlock();
+ setPowerModeWithHandle(handle, Mode::SUSTAINED_PERFORMANCE,
+ static_cast<bool>(data));
+ break;
+ } else if (hintId == PowerHint::VR_MODE) {
+ sp<IPowerAidl> handle = gPowerHalAidl_;
+ lock.unlock();
+ setPowerModeWithHandle(handle, Mode::VR, static_cast<bool>(data));
+ break;
+ } else {
+ ALOGE("Unsupported power hint: %s.", toString(hintId).c_str());
+ return;
+ }
+ }
+ default: {
+ ALOGE("Unknown power HAL state");
+ return;
+ }
+ }
+ if (hintId == PowerHint::INTERACTION) {
+ SurfaceComposerClient::notifyPowerBoost(static_cast<int32_t>(Boost::INTERACTION));
+ }
}
void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType) {
@@ -119,7 +324,7 @@
gLastEventTime[eventType] = eventTime;
// Tell the power HAL when user activity occurs.
- setPowerBoost(Boost::INTERACTION, 0);
+ sendPowerHint(PowerHint::INTERACTION, 0);
}
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -186,7 +391,10 @@
static void nativeInit(JNIEnv* env, jobject obj) {
gPowerManagerServiceObj = env->NewGlobalRef(obj);
- gPowerHalController.init();
+
+ gPowerHalMutex.lock();
+ connectPowerHalLocked();
+ gPowerHalMutex.unlock();
}
static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) {
@@ -199,6 +407,38 @@
release_wake_lock(name.c_str());
}
+static void nativeSetInteractive(JNIEnv* /* env */, jclass /* clazz */, jboolean enable) {
+ std::unique_lock<std::mutex> lock(gPowerHalMutex);
+ switch (connectPowerHalLocked()) {
+ case HalVersion::NONE:
+ return;
+ case HalVersion::HIDL_1_0:
+ FALLTHROUGH_INTENDED;
+ case HalVersion::HIDL_1_1: {
+ android::base::Timer t;
+ sp<IPowerV1_0> handle = gPowerHalHidlV1_0_;
+ lock.unlock();
+ auto ret = handle->setInteractive(enable);
+ processPowerHalReturn(ret.isOk(), "setInteractive");
+ if (t.duration() > 20ms) {
+ ALOGD("Excessive delay in setInteractive(%s) while turning screen %s",
+ enable ? "true" : "false", enable ? "on" : "off");
+ }
+ return;
+ }
+ case HalVersion::AIDL: {
+ sp<IPowerAidl> handle = gPowerHalAidl_;
+ lock.unlock();
+ setPowerModeWithHandle(handle, Mode::INTERACTIVE, enable);
+ return;
+ }
+ default: {
+ ALOGE("Unknown power HAL state");
+ return;
+ }
+ }
+}
+
static void nativeSetAutoSuspend(JNIEnv* /* env */, jclass /* clazz */, jboolean enable) {
if (enable) {
android::base::Timer t;
@@ -215,6 +455,10 @@
}
}
+static void nativeSendPowerHint(JNIEnv* /* env */, jclass /* clazz */, jint hintId, jint data) {
+ sendPowerHint(static_cast<PowerHint>(hintId), data);
+}
+
static void nativeSetPowerBoost(JNIEnv* /* env */, jclass /* clazz */, jint boost,
jint durationMs) {
setPowerBoost(static_cast<Boost>(boost), durationMs);
@@ -225,6 +469,33 @@
return setPowerMode(static_cast<Mode>(mode), enabled);
}
+static void nativeSetFeature(JNIEnv* /* env */, jclass /* clazz */, jint featureId, jint data) {
+ std::unique_lock<std::mutex> lock(gPowerHalMutex);
+ switch (connectPowerHalLocked()) {
+ case HalVersion::NONE:
+ return;
+ case HalVersion::HIDL_1_0:
+ FALLTHROUGH_INTENDED;
+ case HalVersion::HIDL_1_1: {
+ sp<IPowerV1_0> handle = gPowerHalHidlV1_0_;
+ lock.unlock();
+ auto ret = handle->setFeature(static_cast<Feature>(featureId), static_cast<bool>(data));
+ processPowerHalReturn(ret.isOk(), "setFeature");
+ return;
+ }
+ case HalVersion::AIDL: {
+ sp<IPowerAidl> handle = gPowerHalAidl_;
+ lock.unlock();
+ setPowerModeWithHandle(handle, Mode::DOUBLE_TAP_TO_WAKE, static_cast<bool>(data));
+ return;
+ }
+ default: {
+ ALOGE("Unknown power HAL state");
+ return;
+ }
+ }
+}
+
static bool nativeForceSuspend(JNIEnv* /* env */, jclass /* clazz */) {
bool retval = false;
getSuspendControl()->forceSuspend(&retval);
@@ -241,9 +512,12 @@
{"nativeForceSuspend", "()Z", (void*)nativeForceSuspend},
{"nativeReleaseSuspendBlocker", "(Ljava/lang/String;)V",
(void*)nativeReleaseSuspendBlocker},
+ {"nativeSetInteractive", "(Z)V", (void*)nativeSetInteractive},
{"nativeSetAutoSuspend", "(Z)V", (void*)nativeSetAutoSuspend},
+ {"nativeSendPowerHint", "(II)V", (void*)nativeSendPowerHint},
{"nativeSetPowerBoost", "(II)V", (void*)nativeSetPowerBoost},
{"nativeSetPowerMode", "(IZ)Z", (void*)nativeSetPowerMode},
+ {"nativeSetFeature", "(II)V", (void*)nativeSetFeature},
};
#define FIND_CLASS(var, className) \
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 39e7249..6f24e3b 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -24,7 +24,6 @@
#include "BroadcastRadio/Tuner.h"
namespace android {
-int register_android_server_alarm_AlarmManagerService(JNIEnv* env);
int register_android_server_BatteryStatsService(JNIEnv* env);
int register_android_server_ConsumerIrService(JNIEnv *env);
int register_android_server_InputManager(JNIEnv* env);
@@ -86,7 +85,6 @@
register_android_server_SerialService(env);
register_android_server_InputManager(env);
register_android_server_LightsService(env);
- register_android_server_alarm_AlarmManagerService(env);
register_android_server_UsbDeviceManager(env);
register_android_server_UsbMidiDevice(env);
register_android_server_UsbAlsaJackDetector(env);
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index a1bfcdf..4a73efe 100644
--- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -38,6 +38,8 @@
import android.annotation.UserIdInt;
import android.app.Application;
+import android.app.backup.BackupManager;
+import android.app.backup.BackupManager.OperationType;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
@@ -871,7 +873,8 @@
SecurityException.class,
() ->
backupManagerService.requestBackup(
- mUserTwoId, packages, observer, monitor, 0));
+ mUserTwoId, packages, observer, monitor, 0,
+ OperationType.BACKUP));
}
/**
@@ -889,9 +892,11 @@
IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true);
- backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0);
+ backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0,
+ OperationType.BACKUP);
- verify(mUserTwoService).requestBackup(packages, observer, monitor, /* flags */ 0);
+ verify(mUserTwoService).requestBackup(packages, observer, monitor, /* flags */ 0,
+ OperationType.BACKUP);
}
/** Test that the backup service routes methods correctly to the user that requests it. */
@@ -904,9 +909,11 @@
IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class);
setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
- backupManagerService.requestBackup(mUserOneId, packages, observer, monitor, /* flags */ 0);
+ backupManagerService.requestBackup(mUserOneId, packages, observer, monitor, /* flags */ 0,
+ OperationType.BACKUP);
- verify(mUserOneService).requestBackup(packages, observer, monitor, /* flags */ 0);
+ verify(mUserOneService).requestBackup(packages, observer, monitor, /* flags */ 0,
+ OperationType.BACKUP);
}
/** Test that the backup service routes methods correctly to the user that requests it. */
@@ -919,9 +926,11 @@
IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class);
setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
- backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0);
+ backupManagerService.requestBackup(mUserTwoId, packages, observer, monitor, /* flags */ 0,
+ OperationType.BACKUP);
- verify(mUserOneService, never()).requestBackup(packages, observer, monitor, /* flags */ 0);
+ verify(mUserOneService, never()).requestBackup(packages, observer, monitor, /* flags */ 0,
+ OperationType.BACKUP);
}
/**
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java b/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
index 33b8aa7..aa1c668 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowAppBackupUtils.java
@@ -62,12 +62,13 @@
}
@Implementation
- protected static boolean appIsEligibleForBackup(ApplicationInfo app, int userId) {
+ protected static boolean appIsEligibleForBackup(ApplicationInfo app, int userId,
+ int operationType) {
return sAppsEligibleForBackup.contains(app.packageName);
}
@Implementation
- protected static boolean appGetsFullBackup(PackageInfo packageInfo) {
+ protected static boolean appGetsFullBackup(PackageInfo packageInfo, int operationType) {
return sAppsGetFullBackup.contains(packageInfo.packageName);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 730f303..a462dc3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -39,6 +39,7 @@
import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
@@ -56,6 +57,7 @@
import com.android.server.AppStateTracker;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
+import com.android.server.SystemServiceManager;
import com.android.server.job.controllers.JobStatus;
import com.android.server.usage.AppStandbyInternal;
@@ -82,6 +84,7 @@
private class TestJobSchedulerService extends JobSchedulerService {
TestJobSchedulerService(Context context) {
super(context);
+ mAppStateTracker = mock(AppStateTracker.class);
}
@Override
@@ -136,6 +139,9 @@
} catch (RemoteException e) {
fail("registerUidObserver threw exception: " + e.getMessage());
}
+ // Called by QuotaTracker
+ doReturn(mock(SystemServiceManager.class))
+ .when(() -> LocalServices.getService(SystemServiceManager.class));
JobSchedulerService.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
JobSchedulerService.sElapsedRealtimeClock =
@@ -750,4 +756,90 @@
maybeQueueFunctor.postProcess();
assertEquals(3, mService.mPendingJobs.size());
}
+
+ /** Tests that jobs scheduled by the app itself are counted towards scheduling limits. */
+ @Test
+ public void testScheduleLimiting_RegularSchedule_Blocked() {
+ mService.mConstants.ENABLE_API_QUOTAS = true;
+ mService.mConstants.API_QUOTA_SCHEDULE_COUNT = 300;
+ mService.mConstants.API_QUOTA_SCHEDULE_WINDOW_MS = 300000;
+ mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
+ mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
+ mService.updateQuotaTracker();
+
+ final JobInfo job = createJobInfo().setPersisted(true).build();
+ for (int i = 0; i < 500; ++i) {
+ final int expected =
+ i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE;
+ assertEquals("Got unexpected result for schedule #" + (i + 1),
+ expected,
+ mService.scheduleAsPackage(job, null, 10123, null, 0, ""));
+ }
+ }
+
+ /**
+ * Tests that jobs scheduled by the app itself succeed even if the app is above the scheduling
+ * limit.
+ */
+ @Test
+ public void testScheduleLimiting_RegularSchedule_Allowed() {
+ mService.mConstants.ENABLE_API_QUOTAS = true;
+ mService.mConstants.API_QUOTA_SCHEDULE_COUNT = 300;
+ mService.mConstants.API_QUOTA_SCHEDULE_WINDOW_MS = 300000;
+ mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
+ mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
+ mService.updateQuotaTracker();
+
+ final JobInfo job = createJobInfo().setPersisted(true).build();
+ for (int i = 0; i < 500; ++i) {
+ assertEquals("Got unexpected result for schedule #" + (i + 1),
+ JobScheduler.RESULT_SUCCESS,
+ mService.scheduleAsPackage(job, null, 10123, null, 0, ""));
+ }
+ }
+
+ /**
+ * Tests that jobs scheduled through a proxy (eg. system server) don't count towards scheduling
+ * limits.
+ */
+ @Test
+ public void testScheduleLimiting_Proxy() {
+ mService.mConstants.ENABLE_API_QUOTAS = true;
+ mService.mConstants.API_QUOTA_SCHEDULE_COUNT = 300;
+ mService.mConstants.API_QUOTA_SCHEDULE_WINDOW_MS = 300000;
+ mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
+ mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
+ mService.updateQuotaTracker();
+
+ final JobInfo job = createJobInfo().setPersisted(true).build();
+ for (int i = 0; i < 500; ++i) {
+ assertEquals("Got unexpected result for schedule #" + (i + 1),
+ JobScheduler.RESULT_SUCCESS,
+ mService.scheduleAsPackage(job, null, 10123, "proxied.package", 0, ""));
+ }
+ }
+
+ /**
+ * Tests that jobs scheduled by an app for itself as if through a proxy are counted towards
+ * scheduling limits.
+ */
+ @Test
+ public void testScheduleLimiting_SelfProxy() {
+ mService.mConstants.ENABLE_API_QUOTAS = true;
+ mService.mConstants.API_QUOTA_SCHEDULE_COUNT = 300;
+ mService.mConstants.API_QUOTA_SCHEDULE_WINDOW_MS = 300000;
+ mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
+ mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
+ mService.updateQuotaTracker();
+
+ final JobInfo job = createJobInfo().setPersisted(true).build();
+ for (int i = 0; i < 500; ++i) {
+ final int expected =
+ i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE;
+ assertEquals("Got unexpected result for schedule #" + (i + 1),
+ expected,
+ mService.scheduleAsPackage(job, null, 10123, job.getService().getPackageName(),
+ 0, ""));
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 5327bf7..cfa2086 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -91,6 +91,7 @@
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
+import com.android.server.accessibility.magnification.FullScreenMagnificationController;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.WindowManagerInternal;
@@ -167,7 +168,7 @@
@Mock private IAccessibilityInteractionConnection mMockIA11yInteractionConnection;
@Mock private IAccessibilityInteractionConnectionCallback mMockCallback;
@Mock private FingerprintGestureDispatcher mMockFingerprintGestureDispatcher;
- @Mock private MagnificationController mMockMagnificationController;
+ @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController;
@Mock private RemoteCallback.OnResultListener mMockListener;
@Before
@@ -178,8 +179,8 @@
when(mMockSystemSupport.getKeyEventDispatcher()).thenReturn(mMockKeyEventDispatcher);
when(mMockSystemSupport.getFingerprintGestureDispatcher())
.thenReturn(mMockFingerprintGestureDispatcher);
- when(mMockSystemSupport.getMagnificationController())
- .thenReturn(mMockMagnificationController);
+ when(mMockSystemSupport.getFullScreenMagnificationController())
+ .thenReturn(mMockFullScreenMagnificationController);
PowerManager powerManager =
new PowerManager(mMockContext, mMockIPowerManager, mMockIThermalService, mHandler);
@@ -553,7 +554,7 @@
public void getMagnificationScale() {
final int displayId = 1;
final float scale = 2.0f;
- when(mMockMagnificationController.getScale(displayId)).thenReturn(scale);
+ when(mMockFullScreenMagnificationController.getScale(displayId)).thenReturn(scale);
final float result = mServiceConnection.getMagnificationScale(displayId);
assertThat(result, is(scale));
@@ -563,7 +564,7 @@
public void getMagnificationScale_serviceNotBelongCurrentUser_returnNoScale() {
final int displayId = 1;
final float scale = 2.0f;
- when(mMockMagnificationController.getScale(displayId)).thenReturn(scale);
+ when(mMockFullScreenMagnificationController.getScale(displayId)).thenReturn(scale);
when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
final float result = mServiceConnection.getMagnificationScale(displayId);
@@ -577,13 +578,14 @@
doAnswer((invocation) -> {
((Region) invocation.getArguments()[1]).set(region);
return null;
- }).when(mMockMagnificationController).getMagnificationRegion(eq(displayId), any());
- when(mMockMagnificationController.isRegistered(displayId)).thenReturn(false);
+ }).when(mMockFullScreenMagnificationController).getMagnificationRegion(eq(displayId),
+ any());
+ when(mMockFullScreenMagnificationController.isRegistered(displayId)).thenReturn(false);
final Region result = mServiceConnection.getMagnificationRegion(displayId);
assertThat(result, is(region));
- verify(mMockMagnificationController).register(displayId);
- verify(mMockMagnificationController).unregister(displayId);
+ verify(mMockFullScreenMagnificationController).register(displayId);
+ verify(mMockFullScreenMagnificationController).unregister(displayId);
}
@Test
@@ -593,7 +595,8 @@
doAnswer((invocation) -> {
((Region) invocation.getArguments()[1]).set(region);
return null;
- }).when(mMockMagnificationController).getMagnificationRegion(eq(displayId), any());
+ }).when(mMockFullScreenMagnificationController).getMagnificationRegion(eq(displayId),
+ any());
when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
final Region result = mServiceConnection.getMagnificationRegion(displayId);
@@ -604,20 +607,20 @@
public void getMagnificationCenterX_notRegistered_shouldRegisterThenUnregister() {
final int displayId = 1;
final float centerX = 480.0f;
- when(mMockMagnificationController.getCenterX(displayId)).thenReturn(centerX);
- when(mMockMagnificationController.isRegistered(displayId)).thenReturn(false);
+ when(mMockFullScreenMagnificationController.getCenterX(displayId)).thenReturn(centerX);
+ when(mMockFullScreenMagnificationController.isRegistered(displayId)).thenReturn(false);
final float result = mServiceConnection.getMagnificationCenterX(displayId);
assertThat(result, is(centerX));
- verify(mMockMagnificationController).register(displayId);
- verify(mMockMagnificationController).unregister(displayId);
+ verify(mMockFullScreenMagnificationController).register(displayId);
+ verify(mMockFullScreenMagnificationController).unregister(displayId);
}
@Test
public void getMagnificationCenterX_serviceNotBelongCurrentUser_returnZero() {
final int displayId = 1;
final float centerX = 480.0f;
- when(mMockMagnificationController.getCenterX(displayId)).thenReturn(centerX);
+ when(mMockFullScreenMagnificationController.getCenterX(displayId)).thenReturn(centerX);
when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
final float result = mServiceConnection.getMagnificationCenterX(displayId);
@@ -628,20 +631,20 @@
public void getMagnificationCenterY_notRegistered_shouldRegisterThenUnregister() {
final int displayId = 1;
final float centerY = 640.0f;
- when(mMockMagnificationController.getCenterY(displayId)).thenReturn(centerY);
- when(mMockMagnificationController.isRegistered(displayId)).thenReturn(false);
+ when(mMockFullScreenMagnificationController.getCenterY(displayId)).thenReturn(centerY);
+ when(mMockFullScreenMagnificationController.isRegistered(displayId)).thenReturn(false);
final float result = mServiceConnection.getMagnificationCenterY(displayId);
assertThat(result, is(centerY));
- verify(mMockMagnificationController).register(displayId);
- verify(mMockMagnificationController).unregister(displayId);
+ verify(mMockFullScreenMagnificationController).register(displayId);
+ verify(mMockFullScreenMagnificationController).unregister(displayId);
}
@Test
public void getMagnificationCenterY_serviceNotBelongCurrentUser_returnZero() {
final int displayId = 1;
final float centerY = 640.0f;
- when(mMockMagnificationController.getCenterY(displayId)).thenReturn(centerY);
+ when(mMockFullScreenMagnificationController.getCenterY(displayId)).thenReturn(centerY);
when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
final float result = mServiceConnection.getMagnificationCenterY(displayId);
@@ -651,7 +654,7 @@
@Test
public void resetMagnification() {
final int displayId = 1;
- when(mMockMagnificationController.reset(displayId, true)).thenReturn(true);
+ when(mMockFullScreenMagnificationController.reset(displayId, true)).thenReturn(true);
final boolean result = mServiceConnection.resetMagnification(displayId, true);
assertThat(result, is(true));
@@ -660,7 +663,7 @@
@Test
public void resetMagnification_cantControlMagnification_returnFalse() {
final int displayId = 1;
- when(mMockMagnificationController.reset(displayId, true)).thenReturn(true);
+ when(mMockFullScreenMagnificationController.reset(displayId, true)).thenReturn(true);
when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false);
final boolean result = mServiceConnection.resetMagnification(displayId, true);
@@ -670,7 +673,7 @@
@Test
public void resetMagnification_serviceNotBelongCurrentUser_returnFalse() {
final int displayId = 1;
- when(mMockMagnificationController.reset(displayId, true)).thenReturn(true);
+ when(mMockFullScreenMagnificationController.reset(displayId, true)).thenReturn(true);
when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
final boolean result = mServiceConnection.resetMagnification(displayId, true);
@@ -683,14 +686,14 @@
final float scale = 1.8f;
final float centerX = 50.5f;
final float centerY = 100.5f;
- when(mMockMagnificationController.setScaleAndCenter(displayId,
+ when(mMockFullScreenMagnificationController.setScaleAndCenter(displayId,
scale, centerX, centerY, true, SERVICE_ID)).thenReturn(true);
- when(mMockMagnificationController.isRegistered(displayId)).thenReturn(false);
+ when(mMockFullScreenMagnificationController.isRegistered(displayId)).thenReturn(false);
final boolean result = mServiceConnection.setMagnificationScaleAndCenter(
displayId, scale, centerX, centerY, true);
assertThat(result, is(true));
- verify(mMockMagnificationController).register(displayId);
+ verify(mMockFullScreenMagnificationController).register(displayId);
}
@Test
@@ -699,7 +702,7 @@
final float scale = 1.8f;
final float centerX = 50.5f;
final float centerY = 100.5f;
- when(mMockMagnificationController.setScaleAndCenter(displayId,
+ when(mMockFullScreenMagnificationController.setScaleAndCenter(displayId,
scale, centerX, centerY, true, SERVICE_ID)).thenReturn(true);
when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false);
@@ -714,7 +717,7 @@
final float scale = 1.8f;
final float centerX = 50.5f;
final float centerY = 100.5f;
- when(mMockMagnificationController.setScaleAndCenter(displayId,
+ when(mMockFullScreenMagnificationController.setScaleAndCenter(displayId,
scale, centerX, centerY, true, SERVICE_ID)).thenReturn(true);
when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
index e166935..4b2a9fc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
@@ -52,6 +52,8 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.accessibility.gestures.TouchExplorer;
+import com.android.server.accessibility.magnification.FullScreenMagnificationController;
+import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler;
@@ -89,7 +91,7 @@
FullScreenMagnificationGestureHandler.class, TouchExplorer.class,
AutoclickController.class, AccessibilityInputFilter.class};
- private MagnificationController mMockMagnificationController;
+ private FullScreenMagnificationController mMockFullScreenMagnificationController;
private AccessibilityManagerService mAms;
private AccessibilityInputFilter mA11yInputFilter;
private EventCaptor mCaptor1;
@@ -135,12 +137,13 @@
setDisplayCount(1);
mAms = spy(new AccessibilityManagerService(context));
- mMockMagnificationController = mock(MagnificationController.class);
+ mMockFullScreenMagnificationController = mock(FullScreenMagnificationController.class);
mA11yInputFilter = new AccessibilityInputFilter(context, mAms, mEventHandler);
mA11yInputFilter.onInstalled();
when(mAms.getValidDisplayList()).thenReturn(mDisplayList);
- when(mAms.getMagnificationController()).thenReturn(mMockMagnificationController);
+ when(mAms.getFullScreenMagnificationController()).thenReturn(
+ mMockFullScreenMagnificationController);
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index b6cf278..27edfd4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -44,6 +44,7 @@
import android.testing.DexmakerShareClassLoaderRule;
import android.view.Display;
+import com.android.server.accessibility.magnification.FullScreenMagnificationController;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -87,7 +88,8 @@
@Mock WindowManagerInternal mMockWindowManagerInternal;
@Mock SystemActionPerformer mMockSystemActionPerformer;
@Mock KeyEventDispatcher mMockKeyEventDispatcher;
- @Mock MagnificationController mMockMagnificationController;
+ @Mock
+ FullScreenMagnificationController mMockFullScreenMagnificationController;
@Mock IBinder mMockIBinder;
@Mock IAccessibilityServiceClient mMockServiceClient;
@Mock MotionEventInjector mMockMotionEventInjector;
@@ -98,8 +100,8 @@
public void setup() {
MockitoAnnotations.initMocks(this);
when(mMockSystemSupport.getKeyEventDispatcher()).thenReturn(mMockKeyEventDispatcher);
- when(mMockSystemSupport.getMagnificationController())
- .thenReturn(mMockMagnificationController);
+ when(mMockSystemSupport.getFullScreenMagnificationController())
+ .thenReturn(mMockFullScreenMagnificationController);
when(mMockSystemSupport.getMotionEventInjectorForDisplayLocked(
Display.DEFAULT_DISPLAY)).thenReturn(mMockMotionEventInjector);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
similarity index 82%
rename from services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
rename to services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 82c6498..a9f2e4a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.accessibility;
+package com.android.server.accessibility.magnification;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -47,6 +47,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
@@ -66,7 +67,7 @@
import java.util.Locale;
@RunWith(AndroidJUnit4.class)
-public class MagnificationControllerTest {
+public class FullScreenMagnificationControllerTest {
static final Rect INITIAL_MAGNIFICATION_BOUNDS = new Rect(0, 0, 100, 200);
static final PointF INITIAL_MAGNIFICATION_BOUNDS_CENTER = new PointF(
INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY());
@@ -85,8 +86,8 @@
static final int DISPLAY_COUNT = 2;
static final int INVALID_DISPLAY = 2;
- final MagnificationController.ControllerContext mMockControllerCtx =
- mock(MagnificationController.ControllerContext.class);
+ final FullScreenMagnificationController.ControllerContext mMockControllerCtx =
+ mock(FullScreenMagnificationController.ControllerContext.class);
final Context mMockContext = mock(Context.class);
final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
@@ -95,7 +96,7 @@
ValueAnimator mMockValueAnimator;
ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
- MagnificationController mMagnificationController;
+ FullScreenMagnificationController mFullScreenMagnificationController;
@Before
public void setUp() {
@@ -109,7 +110,8 @@
when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
initMockWindowManager();
- mMagnificationController = new MagnificationController(mMockControllerCtx, new Object());
+ mFullScreenMagnificationController = new FullScreenMagnificationController(
+ mMockControllerCtx, new Object());
}
@After
@@ -131,23 +133,23 @@
eq(DISPLAY_1), (MagnificationCallbacks) anyObject());
verify(mMockWindowManager).setMagnificationCallbacks(
eq(INVALID_DISPLAY), (MagnificationCallbacks) anyObject());
- assertTrue(mMagnificationController.isRegistered(DISPLAY_0));
- assertTrue(mMagnificationController.isRegistered(DISPLAY_1));
- assertFalse(mMagnificationController.isRegistered(INVALID_DISPLAY));
+ assertTrue(mFullScreenMagnificationController.isRegistered(DISPLAY_0));
+ assertTrue(mFullScreenMagnificationController.isRegistered(DISPLAY_1));
+ assertFalse(mFullScreenMagnificationController.isRegistered(INVALID_DISPLAY));
}
@Test
public void testRegister_WindowManagerAndContextUnregisterListeners() {
register(DISPLAY_0);
register(DISPLAY_1);
- mMagnificationController.unregister(DISPLAY_0);
+ mFullScreenMagnificationController.unregister(DISPLAY_0);
verify(mMockContext, times(0)).unregisterReceiver((BroadcastReceiver) anyObject());
- mMagnificationController.unregister(DISPLAY_1);
+ mFullScreenMagnificationController.unregister(DISPLAY_1);
verify(mMockContext).unregisterReceiver((BroadcastReceiver) anyObject());
verify(mMockWindowManager).setMagnificationCallbacks(eq(DISPLAY_0), eq(null));
verify(mMockWindowManager).setMagnificationCallbacks(eq(DISPLAY_1), eq(null));
- assertFalse(mMagnificationController.isRegistered(DISPLAY_0));
- assertFalse(mMagnificationController.isRegistered(DISPLAY_1));
+ assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_0));
+ assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_1));
}
@Test
@@ -166,14 +168,14 @@
Rect initialBounds = new Rect();
assertEquals(expectedInitialSpec, getCurrentMagnificationSpec(displayId));
- mMagnificationController.getMagnificationRegion(displayId, initialMagRegion);
- mMagnificationController.getMagnificationBounds(displayId, initialBounds);
+ mFullScreenMagnificationController.getMagnificationRegion(displayId, initialMagRegion);
+ mFullScreenMagnificationController.getMagnificationBounds(displayId, initialBounds);
assertEquals(INITIAL_MAGNIFICATION_REGION, initialMagRegion);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS, initialBounds);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerX(),
- mMagnificationController.getCenterX(displayId), 0.0f);
+ mFullScreenMagnificationController.getCenterX(displayId), 0.0f);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerY(),
- mMagnificationController.getCenterY(displayId), 0.0f);
+ mFullScreenMagnificationController.getCenterY(displayId), 0.0f);
}
@Test
@@ -185,24 +187,26 @@
}
private void notRegistered_publicMethodsShouldBeBenign(int displayId) {
- assertFalse(mMagnificationController.isMagnifying(displayId));
- assertFalse(mMagnificationController.magnificationRegionContains(displayId, 100, 100));
- assertFalse(mMagnificationController.reset(displayId, true));
- assertFalse(mMagnificationController.setScale(displayId, 2, 100, 100, true, 0));
- assertFalse(mMagnificationController.setCenter(displayId, 100, 100, false, 1));
- assertFalse(mMagnificationController.setScaleAndCenter(displayId,
+ assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
+ assertFalse(
+ mFullScreenMagnificationController.magnificationRegionContains(displayId, 100,
+ 100));
+ assertFalse(mFullScreenMagnificationController.reset(displayId, true));
+ assertFalse(mFullScreenMagnificationController.setScale(displayId, 2, 100, 100, true, 0));
+ assertFalse(mFullScreenMagnificationController.setCenter(displayId, 100, 100, false, 1));
+ assertFalse(mFullScreenMagnificationController.setScaleAndCenter(displayId,
1.5f, 100, 100, false, 2));
- assertTrue(mMagnificationController.getIdOfLastServiceToMagnify(displayId) < 0);
+ assertTrue(mFullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId) < 0);
- mMagnificationController.getMagnificationRegion(displayId, new Region());
- mMagnificationController.getMagnificationBounds(displayId, new Rect());
- mMagnificationController.getScale(displayId);
- mMagnificationController.getOffsetX(displayId);
- mMagnificationController.getOffsetY(displayId);
- mMagnificationController.getCenterX(displayId);
- mMagnificationController.getCenterY(displayId);
- mMagnificationController.offsetMagnifiedRegion(displayId, 50, 50, 1);
- mMagnificationController.unregister(displayId);
+ mFullScreenMagnificationController.getMagnificationRegion(displayId, new Region());
+ mFullScreenMagnificationController.getMagnificationBounds(displayId, new Rect());
+ mFullScreenMagnificationController.getScale(displayId);
+ mFullScreenMagnificationController.getOffsetX(displayId);
+ mFullScreenMagnificationController.getOffsetY(displayId);
+ mFullScreenMagnificationController.getCenterX(displayId);
+ mFullScreenMagnificationController.getCenterY(displayId);
+ mFullScreenMagnificationController.offsetMagnifiedRegion(displayId, 50, 50, 1);
+ mFullScreenMagnificationController.unregister(displayId);
}
@Test
@@ -218,7 +222,7 @@
final float scale = 2.0f;
final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
final PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale);
- assertTrue(mMagnificationController
+ assertTrue(mFullScreenMagnificationController
.setScale(displayId, scale, center.x, center.y, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
@@ -226,8 +230,8 @@
verify(mMockWindowManager).setMagnificationSpec(
eq(displayId), argThat(closeTo(expectedSpec)));
assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedSpec));
- assertEquals(center.x, mMagnificationController.getCenterX(displayId), 0.0);
- assertEquals(center.y, mMagnificationController.getCenterY(displayId), 0.0);
+ assertEquals(center.x, mFullScreenMagnificationController.getCenterX(displayId), 0.0);
+ assertEquals(center.y, mFullScreenMagnificationController.getCenterY(displayId), 0.0);
verify(mMockValueAnimator, times(0)).start();
}
@@ -244,7 +248,7 @@
MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
float scale = 2.0f;
PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
- assertTrue(mMagnificationController
+ assertTrue(mFullScreenMagnificationController
.setScale(displayId, scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
@@ -254,8 +258,8 @@
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
- assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mFullScreenMagnificationController.getCenterY(displayId), 0.5);
assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec));
verify(mMockValueAnimator).start();
@@ -291,13 +295,13 @@
register(displayId);
// First zoom in
float scale = 2.0f;
- assertTrue(mMagnificationController.setScale(displayId, scale,
+ assertTrue(mFullScreenMagnificationController.setScale(displayId, scale,
INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY(),
false, SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
- assertTrue(mMagnificationController
+ assertTrue(mFullScreenMagnificationController
.setCenter(displayId, newCenter.x, newCenter.y, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
@@ -305,8 +309,8 @@
verify(mMockWindowManager).setMagnificationSpec(
eq(displayId), argThat(closeTo(expectedSpec)));
- assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.0);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.0);
+ assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.0);
+ assertEquals(newCenter.y, mFullScreenMagnificationController.getCenterY(displayId), 0.0);
verify(mMockValueAnimator, times(0)).start();
}
@@ -326,12 +330,12 @@
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
- assertTrue(mMagnificationController.setScaleAndCenter(displayId, scale, newCenter.x,
- newCenter.y, true, SERVICE_ID_1));
+ assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, scale,
+ newCenter.x, newCenter.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
- assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mFullScreenMagnificationController.getCenterY(displayId), 0.5);
assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec));
verify(mMockAms).notifyMagnificationChanged(displayId,
INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
@@ -370,30 +374,30 @@
MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter,
- MagnificationController.MAX_SCALE);
+ FullScreenMagnificationController.MAX_SCALE);
MagnificationSpec endSpec = getMagnificationSpec(
- MagnificationController.MAX_SCALE, offsets);
+ FullScreenMagnificationController.MAX_SCALE, offsets);
- assertTrue(mMagnificationController.setScaleAndCenter(displayId,
- MagnificationController.MAX_SCALE + 1.0f,
+ assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
+ FullScreenMagnificationController.MAX_SCALE + 1.0f,
newCenter.x, newCenter.y, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
- assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mFullScreenMagnificationController.getCenterY(displayId), 0.5);
verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
Mockito.reset(mMockWindowManager);
// Verify that we can't zoom below 1x
- assertTrue(mMagnificationController.setScaleAndCenter(displayId, 0.5f,
+ assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, 0.5f,
INITIAL_MAGNIFICATION_BOUNDS_CENTER.x, INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.x,
- mMagnificationController.getCenterX(displayId), 0.5);
+ mFullScreenMagnificationController.getCenterX(displayId), 0.5);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
- mMagnificationController.getCenterY(displayId), 0.5);
+ mFullScreenMagnificationController.getCenterY(displayId), 0.5);
verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec)));
}
@@ -410,27 +414,27 @@
float scale = 2.0f;
// Off the edge to the top and left
- assertTrue(mMagnificationController.setScaleAndCenter(displayId,
+ assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
scale, -100f, -200f, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
- assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mFullScreenMagnificationController.getCenterY(displayId), 0.5);
verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
Mockito.reset(mMockWindowManager);
// Off the edge to the bottom and right
- assertTrue(mMagnificationController.setScaleAndCenter(displayId, scale,
+ assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, scale,
INITIAL_MAGNIFICATION_BOUNDS.right + 1, INITIAL_MAGNIFICATION_BOUNDS.bottom + 1,
false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
- assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mFullScreenMagnificationController.getCenterY(displayId), 0.5);
verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
}
@@ -466,7 +470,7 @@
float scale = 2.0f;
PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
// First zoom in
- assertTrue(mMagnificationController
+ assertTrue(mFullScreenMagnificationController
.setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
@@ -474,7 +478,7 @@
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
- mMagnificationController.offsetMagnifiedRegion(displayId,
+ mFullScreenMagnificationController.offsetMagnifiedRegion(displayId,
startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y,
SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
@@ -482,8 +486,8 @@
MagnificationSpec expectedSpec = getMagnificationSpec(scale, newOffsets);
verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(expectedSpec)));
- assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.0);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.0);
+ assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.0);
+ assertEquals(newCenter.y, mFullScreenMagnificationController.getCenterY(displayId), 0.0);
verify(mMockValueAnimator, times(0)).start();
}
@@ -499,9 +503,9 @@
register(displayId);
Mockito.reset(mMockWindowManager);
MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
- mMagnificationController.offsetMagnifiedRegion(displayId, 10, 10, SERVICE_ID_1);
+ mFullScreenMagnificationController.offsetMagnifiedRegion(displayId, 10, 10, SERVICE_ID_1);
assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
- mMagnificationController.offsetMagnifiedRegion(displayId, -10, -10, SERVICE_ID_1);
+ mFullScreenMagnificationController.offsetMagnifiedRegion(displayId, -10, -10, SERVICE_ID_1);
assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@@ -520,24 +524,24 @@
// Upper left edges
PointF ulCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
- assertTrue(mMagnificationController
+ assertTrue(mFullScreenMagnificationController
.setScaleAndCenter(displayId, scale, ulCenter.x, ulCenter.y, false,
SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
MagnificationSpec ulSpec = getCurrentMagnificationSpec(displayId);
- mMagnificationController.offsetMagnifiedRegion(displayId, -10, -10,
+ mFullScreenMagnificationController.offsetMagnifiedRegion(displayId, -10, -10,
SERVICE_ID_1);
assertThat(getCurrentMagnificationSpec(displayId), closeTo(ulSpec));
verifyNoMoreInteractions(mMockWindowManager);
// Lower right edges
PointF lrCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
- assertTrue(mMagnificationController
+ assertTrue(mFullScreenMagnificationController
.setScaleAndCenter(displayId, scale, lrCenter.x, lrCenter.y, false,
SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
MagnificationSpec lrSpec = getCurrentMagnificationSpec(displayId);
- mMagnificationController.offsetMagnifiedRegion(displayId, 10, 10,
+ mFullScreenMagnificationController.offsetMagnifiedRegion(displayId, 10, 10,
SERVICE_ID_1);
assertThat(getCurrentMagnificationSpec(displayId), closeTo(lrSpec));
verifyNoMoreInteractions(mMockWindowManager);
@@ -554,14 +558,16 @@
private void getIdOfLastServiceToChange_returnsCorrectValue(int displayId) {
register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
- assertTrue(mMagnificationController
+ assertTrue(mFullScreenMagnificationController
.setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
SERVICE_ID_1));
- assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify(displayId));
- assertTrue(mMagnificationController
+ assertEquals(SERVICE_ID_1,
+ mFullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId));
+ assertTrue(mFullScreenMagnificationController
.setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
SERVICE_ID_2));
- assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify(displayId));
+ assertEquals(SERVICE_ID_2,
+ mFullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId));
}
@Test
@@ -575,16 +581,16 @@
private void resetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled(int displayId) {
register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
- mMagnificationController
+ mFullScreenMagnificationController
.setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
SERVICE_ID_1);
- mMagnificationController
+ mFullScreenMagnificationController
.setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
SERVICE_ID_2);
- assertFalse(mMagnificationController.resetIfNeeded(displayId, SERVICE_ID_1));
- assertTrue(mMagnificationController.isMagnifying(displayId));
- assertTrue(mMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2));
- assertFalse(mMagnificationController.isMagnifying(displayId));
+ assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_1));
+ assertTrue(mFullScreenMagnificationController.isMagnifying(displayId));
+ assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2));
+ assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
}
@Test
@@ -600,16 +606,16 @@
final int userId2 = 2;
register(displayId);
- mMagnificationController.setUserId(userId1);
+ mFullScreenMagnificationController.setUserId(userId1);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
- SERVICE_ID_1);
+ mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
+ false, SERVICE_ID_1);
- mMagnificationController.setUserId(userId1);
- assertTrue(mMagnificationController.isMagnifying(displayId));
- mMagnificationController.setUserId(userId2);
- assertFalse(mMagnificationController.isMagnifying(displayId));
+ mFullScreenMagnificationController.setUserId(userId1);
+ assertTrue(mFullScreenMagnificationController.isMagnifying(displayId));
+ mFullScreenMagnificationController.setUserId(userId2);
+ assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
}
@Test
@@ -625,11 +631,11 @@
zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
reset(mMockAms);
- assertTrue(mMagnificationController.resetIfNeeded(displayId, false));
+ assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, false));
verify(mMockAms).notifyMagnificationChanged(eq(displayId),
eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyFloat(), anyFloat());
- assertFalse(mMagnificationController.isMagnifying(displayId));
- assertFalse(mMagnificationController.resetIfNeeded(displayId, false));
+ assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
+ assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, false));
}
@Test
@@ -646,8 +652,8 @@
mMessageCapturingHandler.sendAllMessages();
br.onReceive(mMockContext, null);
mMessageCapturingHandler.sendAllMessages();
- assertFalse(mMagnificationController.isMagnifying(DISPLAY_0));
- assertFalse(mMagnificationController.isMagnifying(DISPLAY_1));
+ assertFalse(mFullScreenMagnificationController.isMagnifying(DISPLAY_0));
+ assertFalse(mFullScreenMagnificationController.isMagnifying(DISPLAY_1));
}
@Test
@@ -665,7 +671,7 @@
mMessageCapturingHandler.sendAllMessages();
callbacks.onUserContextChanged();
mMessageCapturingHandler.sendAllMessages();
- assertFalse(mMagnificationController.isMagnifying(displayId));
+ assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
}
@Test
@@ -681,10 +687,10 @@
MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- assertTrue(mMagnificationController.isMagnifying(displayId));
+ assertTrue(mFullScreenMagnificationController.isMagnifying(displayId));
callbacks.onRotationChanged(0);
mMessageCapturingHandler.sendAllMessages();
- assertFalse(mMagnificationController.isMagnifying(displayId));
+ assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
}
@Test
@@ -722,8 +728,8 @@
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
// setting animate parameter to true is differ from zoomIn2xToMiddle()
- mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, true,
- SERVICE_ID_1);
+ mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
+ true, SERVICE_ID_1);
MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
@@ -750,8 +756,8 @@
mMessageCapturingHandler.sendAllMessages();
PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
- SERVICE_ID_1);
+ mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
+ false, SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec)));
@@ -784,8 +790,8 @@
mMessageCapturingHandler.sendAllMessages();
PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, true,
- SERVICE_ID_1);
+ mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
+ true, SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
when(mMockValueAnimator.isRunning()).thenReturn(true);
@@ -947,12 +953,12 @@
MagnificationSpec firstEndSpec = getMagnificationSpec(
scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, firstCenter, scale));
- assertTrue(mMagnificationController.setScaleAndCenter(displayId,
+ assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId,
scale, firstCenter.x, firstCenter.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
- assertEquals(firstCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
- assertEquals(firstCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertEquals(firstCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(firstCenter.y, mFullScreenMagnificationController.getCenterY(displayId), 0.5);
assertThat(getCurrentMagnificationSpec(displayId), closeTo(firstEndSpec));
verify(mMockValueAnimator, times(1)).start();
@@ -977,7 +983,7 @@
PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
MagnificationSpec newEndSpec = getMagnificationSpec(
scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale));
- assertTrue(mMagnificationController.setCenter(displayId,
+ assertTrue(mFullScreenMagnificationController.setCenter(displayId,
newCenter.x, newCenter.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
@@ -1032,7 +1038,7 @@
private void register(int displayId) {
mMockValueAnimator = mock(ValueAnimator.class);
when(mMockControllerCtx.newValueAnimator()).thenReturn(mMockValueAnimator);
- mMagnificationController.register(displayId);
+ mFullScreenMagnificationController.register(displayId);
ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor =
ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
@@ -1043,9 +1049,9 @@
private void zoomIn2xToMiddle(int displayId) {
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
- SERVICE_ID_1);
- assertTrue(mMagnificationController.isMagnifying(displayId));
+ mFullScreenMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y,
+ false, SERVICE_ID_1);
+ assertTrue(mFullScreenMagnificationController.isMagnifying(displayId));
}
private MagnificationCallbacks getMagnificationCallbacks(int displayId) {
@@ -1084,9 +1090,9 @@
}
private MagnificationSpec getCurrentMagnificationSpec(int displayId) {
- return getMagnificationSpec(mMagnificationController.getScale(displayId),
- mMagnificationController.getOffsetX(displayId),
- mMagnificationController.getOffsetY(displayId));
+ return getMagnificationSpec(mFullScreenMagnificationController.getScale(displayId),
+ mFullScreenMagnificationController.getOffsetX(displayId),
+ mFullScreenMagnificationController.getOffsetY(displayId));
}
private MagSpecMatcher closeTo(MagnificationSpec spec) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java
rename to services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 3678d544..008cbed 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.accessibility;
+package com.android.server.accessibility.magnification;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
@@ -51,6 +51,8 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.EventStreamTransformation;
import com.android.server.accessibility.magnification.MagnificationGestureHandler.ScaleChangedListener;
import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
@@ -121,7 +123,7 @@
private static final int DISPLAY_0 = 0;
private Context mContext;
- MagnificationController mMagnificationController;
+ FullScreenMagnificationController mFullScreenMagnificationController;
@Mock
ScaleChangedListener mMockScaleChangedListener;
@@ -135,8 +137,8 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getContext();
- final MagnificationController.ControllerContext mockController =
- mock(MagnificationController.ControllerContext.class);
+ final FullScreenMagnificationController.ControllerContext mockController =
+ mock(FullScreenMagnificationController.ControllerContext.class);
final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
when(mockController.getContext()).thenReturn(mContext);
when(mockController.getAms()).thenReturn(mock(AccessibilityManagerService.class));
@@ -145,7 +147,8 @@
when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
when(mockController.getAnimationDuration()).thenReturn(1000L);
when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
- mMagnificationController = new MagnificationController(mockController, new Object()) {
+ mFullScreenMagnificationController = new FullScreenMagnificationController(mockController,
+ new Object()) {
@Override
public boolean magnificationRegionContains(int displayId, float x, float y) {
return true;
@@ -154,7 +157,7 @@
@Override
void setForceShowMagnifiableBounds(int displayId, boolean show) {}
};
- mMagnificationController.register(DISPLAY_0);
+ mFullScreenMagnificationController.register(DISPLAY_0);
mClock = new OffsettableClock.Stopped();
boolean detectTripleTap = true;
@@ -164,14 +167,14 @@
@After
public void tearDown() {
- mMagnificationController.unregister(DISPLAY_0);
+ mFullScreenMagnificationController.unregister(DISPLAY_0);
}
@NonNull
private FullScreenMagnificationGestureHandler newInstance(boolean detectTripleTap,
boolean detectShortcutTrigger) {
FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler(
- mContext, mMagnificationController, mMockScaleChangedListener,
+ mContext, mFullScreenMagnificationController, mMockScaleChangedListener,
detectTripleTap, detectShortcutTrigger, DISPLAY_0);
mHandler = new TestHandler(h.mDetectingState, mClock) {
@Override
@@ -653,7 +656,7 @@
}
private boolean isZoomed() {
- return mMgh.mMagnificationController.isMagnifying(DISPLAY_0);
+ return mMgh.mFullScreenMagnificationController.isMagnifying(DISPLAY_0);
}
private int tapCount() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index c9e6284..70e6a34 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -32,7 +32,10 @@
import static java.lang.Float.NaN;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
@@ -300,6 +303,26 @@
assertFalse(mWindowMagnificationManager.isConnected());
}
+ @Test
+ public void requestConnection_registerAndUnregisterBroadcastReceiver() {
+ assertTrue(mWindowMagnificationManager.requestConnection(true));
+ verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
+
+ assertTrue(mWindowMagnificationManager.requestConnection(false));
+ verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
+ }
+
+ @Test
+ public void onReceiveScreenOff_removeMagnificationButtonAndDisableWindowMagnification()
+ throws RemoteException {
+ mWindowMagnificationManager.requestConnection(true);
+ mWindowMagnificationManager.mScreenStateReceiver.onReceive(mContext,
+ new Intent(Intent.ACTION_SCREEN_OFF));
+
+ verify(mMockConnection.getConnection()).removeMagnificationButton(TEST_DISPLAY);
+ verify(mMockConnection.getConnection()).disableWindowMagnification(TEST_DISPLAY);
+ }
+
private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) {
final int len = pointersLocation.length;
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 3ebc315..ccb2ea3 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -18,11 +18,25 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.app.backup.BackupManager.OperationType;
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IBackupObserver;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.backup.internal.OnTaskFinishedListener;
+import com.android.server.backup.params.BackupParams;
+import com.android.server.backup.transport.TransportClient;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,15 +46,25 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public class UserBackupManagerServiceTest {
+ private static final String TEST_PACKAGE = "package1";
+ private static final String[] TEST_PACKAGES = new String[] { TEST_PACKAGE };
+
@Mock Context mContext;
+ @Mock IBackupManagerMonitor mBackupManagerMonitor;
+ @Mock IBackupObserver mBackupObserver;
+ @Mock PackageManager mPackageManager;
+ @Mock TransportClient mTransportClient;
+
private TestBackupService mService;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mService = new TestBackupService(mContext);
+ mService = new TestBackupService(mContext, mPackageManager);
+ mService.setEnabled(true);
+ mService.setSetupComplete(true);
}
@Test
@@ -57,11 +81,72 @@
assertThat(mService.isEnabledStatePersisted).isTrue();
}
+ @Test
+ public void getRequestBackupParams_isMigrationAndAppGetsFullBackup() throws Exception {
+ when(mPackageManager.getPackageInfoAsUser(anyString(), anyInt(), anyInt())).thenReturn(
+ getPackageInfo(TEST_PACKAGE));
+ mService.mAppIsEligibleForBackup = true;
+ mService.mAppGetsFullBackup = true;
+
+ BackupParams params = mService.getRequestBackupParams(TEST_PACKAGES, mBackupObserver,
+ mBackupManagerMonitor, /* flags */ 0, OperationType.MIGRATION,
+ mTransportClient, /* transportDirName */ "", OnTaskFinishedListener.NOP);
+
+ assertThat(params.kvPackages).isEmpty();
+ assertThat(params.fullPackages).contains(TEST_PACKAGE);
+ assertThat(params.operationType).isEqualTo(OperationType.MIGRATION);
+ assertThat(mService.mOperationType).isEqualTo(OperationType.MIGRATION);
+ }
+
+ @Test
+ public void getRequestBackupParams_isMigrationAndAppGetsKeyValueBackup() throws Exception {
+ when(mPackageManager.getPackageInfoAsUser(anyString(), anyInt(), anyInt())).thenReturn(
+ getPackageInfo(TEST_PACKAGE));
+ mService.mAppIsEligibleForBackup = true;
+ mService.mAppGetsFullBackup = false;
+
+ BackupParams params = mService.getRequestBackupParams(TEST_PACKAGES, mBackupObserver,
+ mBackupManagerMonitor, /* flags */ 0, OperationType.MIGRATION,
+ mTransportClient, /* transportDirName */ "", OnTaskFinishedListener.NOP);
+
+ assertThat(params.kvPackages).contains(TEST_PACKAGE);
+ assertThat(params.fullPackages).isEmpty();
+ assertThat(params.operationType).isEqualTo(OperationType.MIGRATION);
+ assertThat(mService.mOperationType).isEqualTo(OperationType.MIGRATION);
+ }
+
+ @Test
+ public void getRequestBackupParams_isMigrationAndAppNotEligibleForBackup() throws Exception {
+ when(mPackageManager.getPackageInfoAsUser(anyString(), anyInt(), anyInt())).thenReturn(
+ getPackageInfo(TEST_PACKAGE));
+ mService.mAppIsEligibleForBackup = false;
+ mService.mAppGetsFullBackup = false;
+
+ BackupParams params = mService.getRequestBackupParams(TEST_PACKAGES, mBackupObserver,
+ mBackupManagerMonitor, /* flags */ 0, OperationType.MIGRATION,
+ mTransportClient, /* transportDirName */ "", OnTaskFinishedListener.NOP);
+
+ assertThat(params.kvPackages).isEmpty();
+ assertThat(params.fullPackages).isEmpty();
+ assertThat(params.operationType).isEqualTo(OperationType.MIGRATION);
+ assertThat(mService.mOperationType).isEqualTo(OperationType.MIGRATION);
+ }
+
+ private static PackageInfo getPackageInfo(String packageName) {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.packageName = packageName;
+ return packageInfo;
+ }
+
private static class TestBackupService extends UserBackupManagerService {
boolean isEnabledStatePersisted = false;
+ boolean mAppIsEligibleForBackup = false;
+ boolean mAppGetsFullBackup = false;
+ int mOperationType = 0;
- TestBackupService(Context context) {
- super(context);
+ TestBackupService(Context context, PackageManager packageManager) {
+ super(context, packageManager);
}
@Override
@@ -76,5 +161,18 @@
@Override
void updateStateOnBackupEnabled(boolean wasEnabled, boolean enable) {}
+
+ @Override
+ boolean appIsEligibleForBackup(ApplicationInfo applicationInfo, int userId,
+ @OperationType int operationType) {
+ mOperationType = operationType;
+ return mAppIsEligibleForBackup;
+ }
+
+ @Override
+ boolean appGetsFullBackup(PackageInfo packageInfo, @OperationType int operationType) {
+ mOperationType = operationType;
+ return mAppGetsFullBackup;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
index a901175..201211e 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
@@ -22,6 +22,8 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.app.backup.BackupManager;
+import android.app.backup.BackupManager.OperationType;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -74,7 +76,7 @@
applicationInfo.packageName = TEST_PACKAGE_NAME;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, OperationType.BACKUP);
assertThat(isEligible).isFalse();
}
@@ -89,7 +91,7 @@
applicationInfo.packageName = TEST_PACKAGE_NAME;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, OperationType.BACKUP);
assertThat(isEligible).isFalse();
}
@@ -103,7 +105,7 @@
applicationInfo.packageName = UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, OperationType.BACKUP);
assertThat(isEligible).isFalse();
}
@@ -120,7 +122,7 @@
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, OperationType.BACKUP);
assertThat(isEligible).isTrue();
}
@@ -137,7 +139,7 @@
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, OperationType.BACKUP);
assertThat(isEligible).isTrue();
}
@@ -154,7 +156,7 @@
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, OperationType.BACKUP);
assertThat(isEligible).isTrue();
}
@@ -171,7 +173,7 @@
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, OperationType.BACKUP);
assertThat(isEligible).isFalse();
}
@@ -188,7 +190,7 @@
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, OperationType.BACKUP);
assertThat(isEligible).isFalse();
}
@@ -205,7 +207,31 @@
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
- mMockPackageManagerInternal, mUserId);
+ mMockPackageManagerInternal, mUserId, OperationType.BACKUP);
+
+ assertThat(isEligible).isFalse();
+ }
+
+ @Test
+ public void appIsEligibleForBackup_backupNotAllowedAndInMigration_returnsTrue()
+ throws Exception {
+ ApplicationInfo applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
+ /* flags */ 0, CUSTOM_BACKUP_AGENT_NAME);
+
+ boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
+ mMockPackageManagerInternal, mUserId, OperationType.MIGRATION);
+
+ assertThat(isEligible).isTrue();
+ }
+
+ @Test
+ public void appIsEligibleForBackup_backupNotAllowedForSystemAppAndInMigration_returnsFalse()
+ throws Exception {
+ ApplicationInfo applicationInfo = getApplicationInfo(Process.SYSTEM_UID,
+ /* flags */ 0, CUSTOM_BACKUP_AGENT_NAME);
+
+ boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo,
+ mMockPackageManagerInternal, mUserId, OperationType.MIGRATION);
assertThat(isEligible).isFalse();
}
@@ -337,7 +363,7 @@
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.backupAgentName = null;
- boolean result = AppBackupUtils.appGetsFullBackup(packageInfo);
+ boolean result = AppBackupUtils.appGetsFullBackup(packageInfo, OperationType.BACKUP);
assertThat(result).isTrue();
}
@@ -350,7 +376,7 @@
packageInfo.applicationInfo.backupAgentName = "backup.agent";
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_FULL_BACKUP_ONLY;
- boolean result = AppBackupUtils.appGetsFullBackup(packageInfo);
+ boolean result = AppBackupUtils.appGetsFullBackup(packageInfo, OperationType.BACKUP);
assertThat(result).isTrue();
}
@@ -363,7 +389,31 @@
packageInfo.applicationInfo.backupAgentName = "backup.agent";
packageInfo.applicationInfo.flags = ~ApplicationInfo.FLAG_FULL_BACKUP_ONLY;
- boolean result = AppBackupUtils.appGetsFullBackup(packageInfo);
+ boolean result = AppBackupUtils.appGetsFullBackup(packageInfo, OperationType.BACKUP);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void appGetsFullBackup_withCustomBackupAgentAndWithoutFullBackupOnlyFlagAndInMigration_returnsTrue()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = getApplicationInfo(Process.FIRST_APPLICATION_UID,
+ ~ApplicationInfo.FLAG_FULL_BACKUP_ONLY, CUSTOM_BACKUP_AGENT_NAME);
+
+ boolean result = AppBackupUtils.appGetsFullBackup(packageInfo, OperationType.MIGRATION);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void appGetsFullBackup_systemAppWithCustomBackupAgentAndWithoutFullBackupOnlyFlagAndInMigration_returnsFalse()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = getApplicationInfo(Process.SYSTEM_UID,
+ ~ApplicationInfo.FLAG_FULL_BACKUP_ONLY, CUSTOM_BACKUP_AGENT_NAME);
+
+ boolean result = AppBackupUtils.appGetsFullBackup(packageInfo, OperationType.MIGRATION);
assertThat(result).isFalse();
}
@@ -406,6 +456,50 @@
}
@Test
+ public void appIgnoresIncludeExcludeRules_systemAppAndInMigration_returnsFalse() {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = Process.SYSTEM_UID;
+
+ boolean result = AppBackupUtils.appIgnoresIncludeExcludeRules(applicationInfo,
+ OperationType.MIGRATION);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void appIgnoresIncludeExcludeRules_systemAppInBackup_returnsFalse() {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = Process.SYSTEM_UID;
+
+ boolean result = AppBackupUtils.appIgnoresIncludeExcludeRules(applicationInfo,
+ OperationType.BACKUP);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void appIgnoresIncludeExcludeRules_nonSystemAppInMigration_returnsTrue() {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = Process.FIRST_APPLICATION_UID;
+
+ boolean result = AppBackupUtils.appIgnoresIncludeExcludeRules(applicationInfo,
+ OperationType.MIGRATION);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void appIgnoresIncludeExcludeRules_nonSystemInBackup_returnsFalse() {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = Process.FIRST_APPLICATION_UID;
+
+ boolean result = AppBackupUtils.appIgnoresIncludeExcludeRules(applicationInfo,
+ OperationType.BACKUP);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
public void signaturesMatch_targetIsNull_returnsFalse() throws Exception {
boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1}, null,
mMockPackageManagerInternal);
@@ -693,4 +787,14 @@
signatureBytes[0] = i;
return new Signature(signatureBytes);
}
+
+ private static ApplicationInfo getApplicationInfo(int appUid, int flags,
+ String backupAgentName) {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags = 0;
+ applicationInfo.packageName = TEST_PACKAGE_NAME;
+ applicationInfo.uid = appUid;
+ applicationInfo.backupAgentName = backupAgentName;
+ return applicationInfo;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricServiceBaseTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricServiceBaseTest.java
deleted file mode 100644
index d4b299d..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricServiceBaseTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-@Presubmit
-@SmallTest
-public class BiometricServiceBaseTest {
- private static class TestableBiometricServiceBase extends BiometricServiceBase {
- TestableBiometricServiceBase(Context context) {
- super(context);
- }
-
- @Override
- protected void doTemplateCleanupForUser(int userId) {
- }
-
- @Override
- protected String getTag() {
- return null;
- }
-
- @Override
- protected Object getDaemon() {
- return null;
- }
-
- @Override
- protected BiometricUtils getBiometricUtils() {
- return null;
- }
-
- @Override
- protected boolean hasReachedEnrollmentLimit(int userId) {
- return false;
- }
-
- @Override
- protected void updateActiveGroup(int userId) {
- }
-
- @Override
- protected boolean hasEnrolledBiometrics(int userId) {
- return false;
- }
-
- @Override
- protected String getManageBiometricPermission() {
- return null;
- }
-
- @Override
- protected void checkUseBiometricPermission() {
- }
-
- @Override
- protected boolean checkAppOps(int uid, String opPackageName) {
- return false;
- }
-
- @Override
- protected List<? extends BiometricAuthenticator.Identifier> getEnrolledTemplates(
- int userId) {
- return null;
- }
-
- @Override
- protected int statsModality() {
- return 0;
- }
-
- @Override
- protected int getLockoutMode(int userId) {
- return 0;
- }
- }
-
- private static final int CLIENT_COOKIE = 0xc00c1e;
-
- private BiometricServiceBase mBiometricServiceBase;
-
- @Mock
- private Context mContext;
- @Mock
- private Resources mResources;
- @Mock
- private BiometricAuthenticator.Identifier mIdentifier;
- @Mock
- private ClientMonitor mClient;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- when(mContext.getResources()).thenReturn(mResources);
- when(mResources.getString(anyInt())).thenReturn("");
- when(mClient.getCookie()).thenReturn(CLIENT_COOKIE);
-
- mBiometricServiceBase = new TestableBiometricServiceBase(mContext);
- }
-
- @Test
- public void testHandleEnumerate_doesNotCrash_withNullClient() {
- mBiometricServiceBase.handleEnumerate(mIdentifier, 0 /* remaining */);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 419fb14..9669010 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -369,11 +369,9 @@
}
@Test
- public void testCreateService_initializesNativeServiceAndSetsPowerModes() {
+ public void testCreateService_initializesNativeService() {
PowerManagerService service = createService();
verify(mNativeWrapperMock).nativeInit(same(service));
- verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.INTERACTIVE), eq(true));
- verify(mNativeWrapperMock).nativeSetPowerMode(eq(Mode.DOUBLE_TAP_TO_WAKE), eq(false));
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index e10dfee..0be1bf3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2686,6 +2686,96 @@
}
@Test
+ public void testLockChannelsForOEM_onlyGivenPkg_appDoesNotExistYet() {
+ mHelper.lockChannelsForOEM(new String[] {PKG_O});
+
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
+ mHelper.createNotificationChannel(PKG_N_MR1, 30, b, false, false);
+
+ assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
+ .isImportanceLockedByOEM());
+ assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, 30, b.getId(), false)
+ .isImportanceLockedByOEM());
+ }
+
+ @Test
+ public void testLockChannelsForOEM_channelSpecific_appDoesNotExistYet() {
+ mHelper.lockChannelsForOEM(new String[] {PKG_O + ":b", PKG_O + ":c"});
+
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+ NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
+ // different uids, same package
+ mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
+ mHelper.createNotificationChannel(PKG_O, 3, b, false, false);
+ mHelper.createNotificationChannel(PKG_O, 30, c, true, true);
+
+ assertFalse(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
+ .isImportanceLockedByOEM());
+ assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
+ .isImportanceLockedByOEM());
+ assertTrue(mHelper.getNotificationChannel(PKG_O, 30, c.getId(), false)
+ .isImportanceLockedByOEM());
+ }
+
+ @Test
+ public void testLockChannelsForOEM_onlyGivenPkg_appDoesNotExistYet_restoreData()
+ throws Exception {
+ mHelper.lockChannelsForOEM(new String[] {PKG_O});
+
+ final String xml = "<ranking version=\"1\">\n"
+ + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n"
+ + "<channel id=\"a\" name=\"a\" importance=\"3\"/>"
+ + "<channel id=\"b\" name=\"b\" importance=\"3\"/>"
+ + "</package>"
+ + "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1 + "\" >\n"
+ + "<channel id=\"a\" name=\"a\" importance=\"3\"/>"
+ + "<channel id=\"b\" name=\"b\" importance=\"3\"/>"
+ + "</package>"
+ + "</ranking>";
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, "a", false)
+ .isImportanceLockedByOEM());
+ assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "b", false)
+ .isImportanceLockedByOEM());
+ }
+
+ @Test
+ public void testLockChannelsForOEM_channelSpecific_appDoesNotExistYet_restoreData()
+ throws Exception {
+ mHelper.lockChannelsForOEM(new String[] {PKG_O + ":b", PKG_O + ":c"});
+
+ final String xml = "<ranking version=\"1\">\n"
+ + "<package name=\"" + PKG_O + "\" uid=\"" + 3 + "\" >\n"
+ + "<channel id=\"a\" name=\"a\" importance=\"3\"/>"
+ + "<channel id=\"b\" name=\"b\" importance=\"3\"/>"
+ + "</package>"
+ + "<package name=\"" + PKG_O + "\" uid=\"" + 30 + "\" >\n"
+ + "<channel id=\"c\" name=\"c\" importance=\"3\"/>"
+ + "</package>"
+ + "</ranking>";
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ assertFalse(mHelper.getNotificationChannel(PKG_O, 3, "a", false)
+ .isImportanceLockedByOEM());
+ assertTrue(mHelper.getNotificationChannel(PKG_O, 3, "b", false)
+ .isImportanceLockedByOEM());
+ assertTrue(mHelper.getNotificationChannel(PKG_O, 30, "c", false)
+ .isImportanceLockedByOEM());
+ }
+
+ @Test
public void testLockChannelsForOEM_channelSpecific_clearData() {
NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
mHelper.getImportance(PKG_O, UID_O);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index f6213bd..6b613ca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -28,6 +28,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doCallRealMethod;
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
@@ -56,6 +57,14 @@
mAppTransitionController = new AppTransitionController(mWm, mDisplayContent);
}
+ @Override
+ ActivityRecord createActivityRecord(DisplayContent dc, int windowingMode, int activityType) {
+ final ActivityRecord r = super.createActivityRecord(dc, windowingMode, activityType);
+ // Ensure that ActivityRecord#setOccludesParent takes effect.
+ doCallRealMethod().when(r).fillsParent();
+ return r;
+ }
+
@Test
@FlakyTest(bugId = 131005232)
public void testTranslucentOpen() {
@@ -191,6 +200,9 @@
@Test
public void testGetAnimationTargets_exitingBeforeTransition() {
+ // Create another non-empty task so the animation target won't promote to task display area.
+ WindowTestUtils.createTestActivityRecord(
+ mDisplayContent.getDefaultTaskDisplayArea().getOrCreateRootHomeTask());
final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(stack);
activity.setVisible(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 1d13788..c8b668b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -158,6 +158,7 @@
mActivity.removeImmediately();
}
+ @UseTestDisplay(addWindows = W_ACTIVITY)
@Test
@FlakyTest(bugId = 131005232)
public void testLandscapeSeascapeRotationByApp() {
@@ -188,6 +189,7 @@
appWindow.removeImmediately();
}
+ @UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testLandscapeSeascapeRotationByPolicy() {
// This instance has been spied in {@link TestDisplayContent}.
@@ -295,6 +297,7 @@
mWm.mDisplayFrozen = false;
}
+ @UseTestDisplay
@Test
public void testRespectTopFullscreenOrientation() {
final Configuration displayConfig = mActivity.mDisplayContent.getConfiguration();
@@ -316,6 +319,7 @@
assertEquals(Configuration.ORIENTATION_LANDSCAPE, activityConfig.orientation);
}
+ @UseTestDisplay
@Test
public void testReportOrientationChange() {
mActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
@@ -343,7 +347,7 @@
@Test
public void testAddRemoveRace() {
// There was once a race condition between adding and removing starting windows
- final ActivityRecord appToken = mAppWindow.mActivityRecord;
+ final ActivityRecord appToken = createIsolatedTestActivityRecord();
for (int i = 0; i < 1000; i++) {
appToken.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyTests.java
new file mode 100644
index 0000000..d75b35a
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyTests.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
+import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.DisplayAreaPolicyBuilderTest.SurfacelessDisplayAreaRoot;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests for the {@link DisplayAreaPolicy}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:DisplayAreaPolicyTests
+ */
+@SmallTest
+@Presubmit
+public class DisplayAreaPolicyTests {
+
+ @Rule
+ public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
+
+ private DisplayAreaPolicyBuilder.Result mPolicy;
+ private TaskDisplayArea mTaskDisplayArea1;
+ private TaskDisplayArea mTaskDisplayArea2;
+ private RootDisplayArea mRoot;
+
+ @Before
+ public void setUp() throws Exception {
+ WindowManagerService wms = mSystemServices.getWindowManagerService();
+ mRoot = new SurfacelessDisplayAreaRoot(wms);
+ spyOn(mRoot);
+ DisplayArea<WindowContainer> ime = new DisplayArea<>(wms, ABOVE_TASKS, "Ime");
+ DisplayContent displayContent = mock(DisplayContent.class);
+ doReturn(true).when(displayContent).isTrusted();
+ mTaskDisplayArea1 = new TaskDisplayArea(displayContent, wms, "Tasks1",
+ FEATURE_DEFAULT_TASK_CONTAINER);
+ mTaskDisplayArea2 = new TaskDisplayArea(displayContent, wms, "Tasks2",
+ FEATURE_VENDOR_FIRST);
+ List<TaskDisplayArea> taskDisplayAreaList = new ArrayList<>();
+ taskDisplayAreaList.add(mTaskDisplayArea1);
+ taskDisplayAreaList.add(mTaskDisplayArea2);
+
+ mPolicy = new DisplayAreaPolicyBuilder()
+ .setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
+ .setImeContainer(ime)
+ .setTaskDisplayAreas(taskDisplayAreaList))
+ .build(wms);
+ }
+
+ @Test
+ public void testGetDefaultTaskDisplayArea() {
+ assertEquals(mTaskDisplayArea1, mPolicy.getDefaultTaskDisplayArea());
+ }
+
+ @Test
+ public void testTaskDisplayArea_taskPositionChanged_updatesTaskDisplayAreaPosition() {
+ final ActivityStack stack1 = mTaskDisplayArea1.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final ActivityStack stack2 = mTaskDisplayArea2.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+ // Initial order
+ assertTaskDisplayAreasOrder(mPolicy, mTaskDisplayArea1, mTaskDisplayArea2);
+
+ // Move stack in tda1 to top
+ stack1.getParent().positionChildAt(POSITION_TOP, stack1, true /* includingParents */);
+
+ assertTaskDisplayAreasOrder(mPolicy, mTaskDisplayArea2, mTaskDisplayArea1);
+
+ // Move stack in tda2 to top, but not including parents
+ stack2.getParent().positionChildAt(POSITION_TOP, stack2, false /* includingParents */);
+
+ assertTaskDisplayAreasOrder(mPolicy, mTaskDisplayArea2, mTaskDisplayArea1);
+
+ // Move stack in tda1 to bottom
+ stack1.getParent().positionChildAt(POSITION_BOTTOM, stack1, true /* includingParents */);
+
+ assertTaskDisplayAreasOrder(mPolicy, mTaskDisplayArea1, mTaskDisplayArea2);
+
+ // Move stack in tda2 to bottom, but not including parents
+ stack2.getParent().positionChildAt(POSITION_BOTTOM, stack2, false /* includingParents */);
+
+ assertTaskDisplayAreasOrder(mPolicy, mTaskDisplayArea1, mTaskDisplayArea2);
+ }
+
+ @Test
+ public void testDisplayAreaGroup_taskPositionChanged_updatesDisplayAreaGroupPosition() {
+ final WindowManagerService wms = mSystemServices.getWindowManagerService();
+ final DisplayContent displayContent = mock(DisplayContent.class);
+ doReturn(true).when(displayContent).isTrusted();
+ final RootDisplayArea root = new SurfacelessDisplayAreaRoot(wms);
+ final RootDisplayArea group1 = new SurfacelessDisplayAreaRoot(wms, "group1",
+ FEATURE_VENDOR_FIRST + 1);
+ final RootDisplayArea group2 = new SurfacelessDisplayAreaRoot(wms, "group2",
+ FEATURE_VENDOR_FIRST + 2);
+ final TaskDisplayArea taskDisplayArea1 = new TaskDisplayArea(displayContent, wms, "Tasks1",
+ FEATURE_DEFAULT_TASK_CONTAINER);
+ final TaskDisplayArea taskDisplayArea2 = new TaskDisplayArea(displayContent, wms, "Tasks2",
+ FEATURE_VENDOR_FIRST + 3);
+ final TaskDisplayArea taskDisplayArea3 = new TaskDisplayArea(displayContent, wms, "Tasks3",
+ FEATURE_VENDOR_FIRST + 4);
+ final TaskDisplayArea taskDisplayArea4 = new TaskDisplayArea(displayContent, wms, "Tasks4",
+ FEATURE_VENDOR_FIRST + 5);
+ final TaskDisplayArea taskDisplayArea5 = new TaskDisplayArea(displayContent, wms, "Tasks5",
+ FEATURE_VENDOR_FIRST + 6);
+ final DisplayArea<WindowContainer> ime = new DisplayArea<>(wms, ABOVE_TASKS, "Ime");
+ final DisplayAreaPolicy policy = new DisplayAreaPolicyBuilder()
+ .setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(root)
+ .setImeContainer(ime)
+ .setTaskDisplayAreas(Lists.newArrayList(taskDisplayArea1, taskDisplayArea2))
+ )
+ .addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(group1)
+ .setTaskDisplayAreas(Lists.newArrayList(taskDisplayArea3, taskDisplayArea4))
+ )
+ .addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(group2)
+ .setTaskDisplayAreas(Lists.newArrayList(taskDisplayArea5)))
+ .build(wms);
+ final ActivityStack stack1 = taskDisplayArea1.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final ActivityStack stack3 = taskDisplayArea3.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final ActivityStack stack4 = taskDisplayArea4.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+ // Initial order
+ assertTaskDisplayAreasOrder(policy, taskDisplayArea1, taskDisplayArea2, taskDisplayArea3,
+ taskDisplayArea4, taskDisplayArea5);
+
+ // Move bottom stack in tda1 to top
+ stack1.getParent().positionChildAt(POSITION_TOP, stack1, true /* includingParents */);
+
+ assertTaskDisplayAreasOrder(policy, taskDisplayArea2, taskDisplayArea3, taskDisplayArea4,
+ taskDisplayArea5, taskDisplayArea1);
+
+ // Move bottom stack in tda2 to top
+ stack3.getParent().positionChildAt(POSITION_TOP, stack3, true /* includingParents */);
+
+ assertTaskDisplayAreasOrder(policy, taskDisplayArea2, taskDisplayArea5, taskDisplayArea1,
+ taskDisplayArea4, taskDisplayArea3);
+
+ // Move bottom stack in tda2 to top
+ stack4.getParent().positionChildAt(POSITION_TOP, stack4, true /* includingParents */);
+
+ assertTaskDisplayAreasOrder(policy, taskDisplayArea2, taskDisplayArea5, taskDisplayArea1,
+ taskDisplayArea3, taskDisplayArea4);
+
+ // Move top stack in tda2 to bottom
+ stack4.getParent().positionChildAt(POSITION_BOTTOM, stack4, true /* includingParents */);
+
+ assertTaskDisplayAreasOrder(policy, taskDisplayArea4, taskDisplayArea3, taskDisplayArea2,
+ taskDisplayArea5, taskDisplayArea1);
+ }
+
+ private void assertTaskDisplayAreasOrder(DisplayAreaPolicy policy,
+ TaskDisplayArea... expectTdaOrder) {
+ List<TaskDisplayArea> expectOrder = new ArrayList<>();
+ Collections.addAll(expectOrder, expectTdaOrder);
+
+ // Verify hierarchy
+ List<TaskDisplayArea> actualOrder = new ArrayList<>();
+ policy.mRoot.forAllTaskDisplayAreas(taskDisplayArea -> {
+ actualOrder.add(taskDisplayArea);
+ }, false /* traverseTopToBottom */);
+ assertEquals(expectOrder, actualOrder);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 018b302..792b597 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -133,6 +133,7 @@
@RunWith(WindowTestRunner.class)
public class DisplayContentTests extends WindowTestsBase {
+ @UseTestDisplay(addAllCommonWindows = true)
@Test
public void testForAllWindows() {
final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION,
@@ -159,6 +160,7 @@
mNavBarWindow));
}
+ @UseTestDisplay(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithAppImeTarget() {
final WindowState imeAppTarget =
@@ -180,6 +182,7 @@
mNavBarWindow));
}
+ @UseTestDisplay(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithChildWindowImeTarget() throws Exception {
mDisplayContent.mInputMethodTarget = mChildAppWindowAbove;
@@ -197,6 +200,7 @@
mNavBarWindow));
}
+ @UseTestDisplay(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithStatusBarImeTarget() throws Exception {
mDisplayContent.mInputMethodTarget = mStatusBarWindow;
@@ -214,6 +218,7 @@
mNavBarWindow));
}
+ @UseTestDisplay(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithNotificationShadeImeTarget() throws Exception {
mDisplayContent.mInputMethodTarget = mNotificationShadeWindow;
@@ -231,6 +236,7 @@
mNavBarWindow));
}
+ @UseTestDisplay(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithInBetweenWindowToken() {
// This window is set-up to be z-ordered between some windows that go in the same token like
@@ -252,6 +258,7 @@
mNavBarWindow));
}
+ @UseTestDisplay(addAllCommonWindows = true)
@Test
public void testComputeImeTarget() {
// Verify that an app window can be an ime target.
@@ -271,6 +278,7 @@
assertEquals(childWin, imeTarget);
}
+ @UseTestDisplay(addAllCommonWindows = true)
@Test
public void testComputeImeTarget_startingWindow() {
ActivityRecord activity = createActivityRecord(mDisplayContent,
@@ -775,6 +783,7 @@
.setDisplayInfoOverrideFromWindowManager(dc.getDisplayId(), null);
}
+ @UseTestDisplay
@Test
public void testClearLastFocusWhenReparentingFocusedWindow() {
final DisplayContent defaultDisplay = mWm.getDefaultDisplayContentLocked();
@@ -808,6 +817,7 @@
assertFalse(isOptionsPanelAtRight(landscapeDisplay.getDisplayId()));
}
+ @UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testInputMethodTargetUpdateWhenSwitchingOnDisplays() {
final DisplayContent newDisplay = createNewDisplay();
@@ -907,6 +917,7 @@
public void testComputeImeParent_app() throws Exception {
final DisplayContent dc = createNewDisplay();
dc.mInputMethodTarget = createWindow(null, TYPE_BASE_APPLICATION, "app");
+ dc.mInputMethodInputTarget = dc.mInputMethodTarget;
assertEquals(dc.mInputMethodTarget.mActivityRecord.getSurfaceControl(),
dc.computeImeParent());
}
@@ -920,6 +931,7 @@
assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
}
+ @UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testComputeImeParent_app_notMatchParentBounds() {
spyOn(mAppWindow.mActivityRecord);
@@ -977,6 +989,7 @@
assertNotEquals(dc.mInputMethodInputTarget, dc.computeImeControlTarget());
}
+ @UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testComputeImeControlTarget_notMatchParentBounds() throws Exception {
spyOn(mAppWindow.mActivityRecord);
@@ -1095,6 +1108,7 @@
win.setHasSurface(false);
}
+ @UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_ACTIVITY})
@Test
public void testRequestResizeForEmptyFrames() {
final WindowState win = mChildAppWindowAbove;
@@ -1134,6 +1148,7 @@
is(Configuration.ORIENTATION_PORTRAIT));
}
+ @UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR })
@Test
public void testApplyTopFixedRotationTransform() {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
@@ -1233,7 +1248,9 @@
@Test
public void testFinishFixedRotationNoAppTransitioningTask() {
- final ActivityRecord app = mAppWindow.mActivityRecord;
+ unblockDisplayRotation(mDisplayContent);
+ final ActivityRecord app = createActivityRecord(mDisplayContent, WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD);
final Task task = app.getTask();
final ActivityRecord app2 = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
.setTask(task).build();
@@ -1254,6 +1271,7 @@
assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
}
+ @UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testRotateSeamlesslyWithFixedRotation() {
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
@@ -1274,13 +1292,14 @@
@Test
public void testNoFixedRotationWithPip() {
+ final DisplayContent displayContent = mDefaultDisplay;
+ unblockDisplayRotation(displayContent);
// Make resume-top really update the activity state.
setBooted(mWm.mAtmService);
// Speed up the test by a few seconds.
mWm.mAtmService.deferWindowLayout();
doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
- final DisplayContent displayContent = mWm.mRoot.getDefaultDisplay();
final Configuration displayConfig = displayContent.getConfiguration();
final ActivityRecord pinnedActivity = createActivityRecord(displayContent,
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
@@ -1323,11 +1342,13 @@
@Test
public void testRecentsNotRotatingWithFixedRotation() {
+ unblockDisplayRotation(mDisplayContent);
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
- doCallRealMethod().when(displayRotation).updateRotationUnchecked(anyBoolean());
// Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
+ final ActivityRecord activity = createActivityRecord(mDisplayContent,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
final ActivityRecord recentsActivity = createActivityRecord(mDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS);
recentsActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
@@ -1344,12 +1365,12 @@
// Rotation can be updated if the recents animation is animating but it is not on top, e.g.
// switching activities in different orientations by quickstep gesture.
mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
- mDisplayContent.setFixedRotationLaunchingAppUnchecked(mAppWindow.mActivityRecord);
+ mDisplayContent.setFixedRotationLaunchingAppUnchecked(activity);
displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
assertTrue(displayRotation.updateRotationUnchecked(false));
// The recents activity should not apply fixed rotation if the top activity is not opaque.
- mDisplayContent.mFocusedApp = mAppWindow.mActivityRecord;
+ mDisplayContent.mFocusedApp = activity;
doReturn(false).when(mDisplayContent.mFocusedApp).occludesParent();
doReturn(ROTATION_90).when(mDisplayContent).rotationForActivityInDifferentOrientation(
eq(recentsActivity));
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
index 39cd76a..402fd22 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
@@ -36,6 +36,8 @@
@SmallTest
@Presubmit
+@WindowTestsBase.UseTestDisplay(
+ addWindows = { WindowTestsBase.W_STATUS_BAR, WindowTestsBase.W_NAVIGATION_BAR })
@RunWith(WindowTestRunner.class)
public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index da7c41a7..f9de379 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -49,7 +49,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -65,14 +64,12 @@
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.InsetsState;
-import android.view.View;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Type;
import android.view.WindowManager;
import androidx.test.filters.SmallTest;
-import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.utils.WmDisplayCutout;
import org.junit.Before;
@@ -90,6 +87,8 @@
*/
@SmallTest
@Presubmit
+@WindowTestsBase.UseTestDisplay(
+ addWindows = { WindowTestsBase.W_STATUS_BAR, WindowTestsBase.W_NAVIGATION_BAR })
@RunWith(WindowTestRunner.class)
public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
@@ -120,9 +119,6 @@
// Disabling this call for most tests since it can override the systemUiFlags when called.
doReturn(0).when(mDisplayPolicy).updateSystemUiVisibilityLw();
- mDisplayPolicy.mLastSystemUiFlags |= View.STATUS_BAR_TRANSPARENT;
- mDisplayPolicy.mLastSystemUiFlags |= View.NAVIGATION_BAR_TRANSPARENT;
-
updateDisplayFrames();
}
@@ -146,10 +142,7 @@
mFrames = createDisplayFrames();
mDisplayBounds.set(0, 0, mFrames.mDisplayWidth, mFrames.mDisplayHeight);
mDisplayContent.mDisplayFrames = mFrames;
-
- doReturn(mDisplayBounds).when(mStatusBarWindow).getBounds();
- doReturn(mDisplayBounds).when(mNavBarWindow).getBounds();
- doReturn(mDisplayBounds).when(mWindow).getBounds();
+ mDisplayContent.setBounds(mDisplayBounds);
}
private DisplayFrames createDisplayFrames() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index a3f9b2e..4483f8c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -222,6 +222,7 @@
opaqueDarkNavBar, imeDrawLightNavBar, imeDrawLightNavBar));
}
+ @UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testComputeTopFullscreenOpaqueWindow() {
final WindowManager.LayoutParams attrs = mAppWindow.mAttrs;
@@ -320,6 +321,8 @@
return win;
}
+ @UseTestDisplay(
+ addWindows = { W_ACTIVITY, W_STATUS_BAR, W_NAVIGATION_BAR, W_NOTIFICATION_SHADE })
@Test
public void testUpdateHideNavInputEventReceiver() {
final InsetsPolicy insetsPolicy = mDisplayContent.getInsetsPolicy();
@@ -358,6 +361,7 @@
assertNull(displayPolicy.mInputConsumer);
}
+ @UseTestDisplay(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
@Test
public void testImeMinimalSourceFrame() {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
index 1e1c399..b4e1c37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
@@ -68,6 +68,10 @@
@Before
public void setUpDisplayPolicy() {
+ // Disable surface placement because it has no direct relation to layout policy and it also
+ // avoids some noises such as the display info is modified, screen frozen, config change.
+ mWm.mWindowPlacerLocked.deferLayout();
+
mDisplayPolicy = mDisplayContent.getDisplayPolicy();
spyOn(mDisplayPolicy);
@@ -100,6 +104,9 @@
mNavBarWindow.mAttrs.gravity = Gravity.BOTTOM;
addWindow(mNavBarWindow);
mDisplayPolicy.mLastSystemUiFlags |= View.NAVIGATION_BAR_TRANSPARENT;
+
+ // Update source frame and visibility of insets providers.
+ mDisplayContent.getInsetsStateController().onPostLayout();
}
void addWindow(WindowState win) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index 11c02c2..a3d3739a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -77,6 +77,7 @@
*/
@SmallTest
@Presubmit
+@WindowTestsBase.UseTestDisplay
@RunWith(WindowTestRunner.class)
public class DisplayWindowSettingsTests extends WindowTestsBase {
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 87bc7f1..555906d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -225,6 +225,7 @@
assertEquals(ITYPE_STATUS_BAR, fullscreenAppControls[0].getType());
}
+ @UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testShowTransientBars_bothCanBeTransient_appGetsBothFakeControls() {
addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
@@ -259,6 +260,7 @@
.getSource(ITYPE_NAVIGATION_BAR).isVisible());
}
+ @UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl() {
addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
@@ -288,6 +290,7 @@
}
}
+ @UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testAbortTransientBars_bothCanBeAborted_appGetsBothRealControls() {
addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 0a27e1a..5e83e66 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -147,6 +147,7 @@
assertNull(getController().getInsetsForDispatch(app).peekSource(ITYPE_NAVIGATION_BAR));
}
+ @UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_independentSources() {
getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
@@ -162,6 +163,7 @@
assertTrue(getController().getInsetsForDispatch(app1).getSource(ITYPE_IME).isVisible());
}
+ @UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_belowIme() {
getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
@@ -173,6 +175,7 @@
assertTrue(getController().getInsetsForDispatch(app).getSource(ITYPE_IME).isVisible());
}
+ @UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_aboveIme() {
getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
@@ -184,6 +187,7 @@
assertFalse(getController().getInsetsForDispatch(app).getSource(ITYPE_IME).isVisible());
}
+ @UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_imeOrderChanged() {
// This can be the IME z-order target while app cannot be the IME z-order target.
@@ -232,6 +236,7 @@
assertTrue(getController().getInsetsForDispatch(app).getSource(ITYPE_IME).isVisible());
}
+ @UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_childWindow_altFocusable() {
getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
@@ -249,6 +254,7 @@
assertFalse(getController().getInsetsForDispatch(child).getSource(ITYPE_IME).isVisible());
}
+ @UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_childWindow_splitScreen() {
getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index ce3f270..bf84aec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
@@ -55,102 +54,10 @@
mTransaction = spy(StubTransaction.class);
}
- private static final int TOP_BAR = 0x1;
- private static final int BOTTOM_BAR = 0x2;
- private static final int LEFT_BAR = 0x4;
- private static final int RIGHT_BAR = 0x8;
-
@Test
- public void testNotIntersectsOrFullyContains_usesGlobalCoordinates() {
- final Rect outer = new Rect(0, 0, 10, 50);
- final Point surfaceOrig = new Point(1000, 2000);
-
- final Rect topBar = new Rect(0, 0, 10, 2);
- final Rect bottomBar = new Rect(0, 45, 10, 50);
- final Rect leftBar = new Rect(0, 0, 2, 50);
- final Rect rightBar = new Rect(8, 0, 10, 50);
-
- final LetterboxLayoutVerifier verifier =
- new LetterboxLayoutVerifier(outer, surfaceOrig, mLetterbox);
- verifier.setBarRect(topBar, bottomBar, leftBar, rightBar);
-
- // top
- verifier.setInner(0, 2, 10, 50).verifyPositions(TOP_BAR | BOTTOM_BAR, BOTTOM_BAR);
- // bottom
- verifier.setInner(0, 0, 10, 45).verifyPositions(TOP_BAR | BOTTOM_BAR, TOP_BAR);
- // left
- verifier.setInner(2, 0, 10, 50).verifyPositions(LEFT_BAR | RIGHT_BAR, RIGHT_BAR);
- // right
- verifier.setInner(0, 0, 8, 50).verifyPositions(LEFT_BAR | RIGHT_BAR, LEFT_BAR);
- // top + bottom
- verifier.setInner(0, 2, 10, 45).verifyPositions(TOP_BAR | BOTTOM_BAR, 0);
- // left + right
- verifier.setInner(2, 0, 8, 50).verifyPositions(LEFT_BAR | RIGHT_BAR, 0);
- // top + left
- verifier.setInner(2, 2, 10, 50).verifyPositions(TOP_BAR | LEFT_BAR, 0);
- // top + left + right
- verifier.setInner(2, 2, 8, 50).verifyPositions(TOP_BAR | LEFT_BAR | RIGHT_BAR, 0);
- // left + right + bottom
- verifier.setInner(2, 0, 8, 45).verifyPositions(LEFT_BAR | RIGHT_BAR | BOTTOM_BAR, 0);
- // all
- verifier.setInner(2, 2, 8, 45)
- .verifyPositions(TOP_BAR | BOTTOM_BAR | LEFT_BAR | RIGHT_BAR, 0);
- }
-
- private static class LetterboxLayoutVerifier {
- final Rect mOuter;
- final Rect mInner = new Rect();
- final Point mSurfaceOrig;
- final Letterbox mLetterbox;
- final Rect mTempRect = new Rect();
-
- final Rect mTop = new Rect();
- final Rect mBottom = new Rect();
- final Rect mLeft = new Rect();
- final Rect mRight = new Rect();
-
- LetterboxLayoutVerifier(Rect outer, Point surfaceOrig, Letterbox letterbox) {
- mOuter = new Rect(outer);
- mSurfaceOrig = new Point(surfaceOrig);
- mLetterbox = letterbox;
- }
-
- LetterboxLayoutVerifier setInner(int left, int top, int right, int bottom) {
- mInner.set(left, top, right, bottom);
- mLetterbox.layout(mOuter, mInner, mSurfaceOrig);
- return this;
- }
-
- void setBarRect(Rect top, Rect bottom, Rect left, Rect right) {
- mTop.set(top);
- mBottom.set(bottom);
- mLeft.set(left);
- mRight.set(right);
- }
-
- void verifyPositions(int allowedPos, int noOverlapPos) {
- assertEquals(mLetterbox.notIntersectsOrFullyContains(mTop),
- (allowedPos & TOP_BAR) != 0);
- assertEquals(mLetterbox.notIntersectsOrFullyContains(mBottom),
- (allowedPos & BOTTOM_BAR) != 0);
- assertEquals(mLetterbox.notIntersectsOrFullyContains(mLeft),
- (allowedPos & LEFT_BAR) != 0);
- assertEquals(mLetterbox.notIntersectsOrFullyContains(mRight),
- (allowedPos & RIGHT_BAR) != 0);
-
- mTempRect.set(mTop.left, mTop.top, mTop.right, mTop.bottom + 1);
- assertEquals(mLetterbox.notIntersectsOrFullyContains(mTempRect),
- (noOverlapPos & TOP_BAR) != 0);
- mTempRect.set(mLeft.left, mLeft.top, mLeft.right + 1, mLeft.bottom);
- assertEquals(mLetterbox.notIntersectsOrFullyContains(mTempRect),
- (noOverlapPos & LEFT_BAR) != 0);
- mTempRect.set(mRight.left - 1, mRight.top, mRight.right, mRight.bottom);
- assertEquals(mLetterbox.notIntersectsOrFullyContains(mTempRect),
- (noOverlapPos & RIGHT_BAR) != 0);
- mTempRect.set(mBottom.left, mBottom.top - 1, mBottom.right, mBottom.bottom);
- assertEquals(mLetterbox.notIntersectsOrFullyContains(mTempRect),
- (noOverlapPos & BOTTOM_BAR) != 0);
- }
+ public void testOverlappingWith_usesGlobalCoordinates() {
+ mLetterbox.layout(new Rect(0, 0, 10, 50), new Rect(0, 2, 10, 45), new Point(1000, 2000));
+ assertTrue(mLetterbox.isOverlappingWith(new Rect(0, 0, 1, 1)));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 8e85e7b..4fbdd61 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -92,7 +92,6 @@
@Mock RecentsAnimationController.RecentsAnimationCallbacks mAnimationCallbacks;
@Mock TaskSnapshot mMockTaskSnapshot;
private RecentsAnimationController mController;
- private DisplayContent mDefaultDisplay;
private ActivityStack mRootHomeTask;
@Before
@@ -100,7 +99,6 @@
MockitoAnnotations.initMocks(this);
doNothing().when(mWm.mRoot).performSurfacePlacement();
when(mMockRunner.asBinder()).thenReturn(new Binder());
- mDefaultDisplay = mWm.mRoot.getDefaultDisplay();
mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
DEFAULT_DISPLAY));
mRootHomeTask = mDefaultDisplay.getDefaultTaskDisplayArea().getRootHomeTask();
@@ -321,6 +319,7 @@
@Test
public void testRecentViewInFixedPortraitWhenTopAppInLandscape() {
+ unblockDisplayRotation(mDefaultDisplay);
mWm.setRecentsAnimationController(mController);
final ActivityRecord homeActivity = createHomeActivity();
@@ -365,6 +364,7 @@
@Test
public void testClearFixedRotationLaunchingAppAfterCleanupAnimation() {
+ unblockDisplayRotation(mDefaultDisplay);
final ActivityRecord homeActivity = createHomeActivity();
homeActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
final ActivityRecord activity = createActivityRecord(mDefaultDisplay,
@@ -389,6 +389,7 @@
@Test
public void testWallpaperHasFixedRotationApplied() {
+ unblockDisplayRotation(mDefaultDisplay);
mWm.setRecentsAnimationController(mController);
// Create a portrait home activity, a wallpaper and a landscape activity displayed on top.
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index d9ec0b1..4f5b3ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -292,11 +292,8 @@
assertEquals(originalStackCount + 1, defaultTaskDisplayArea.getStackCount());
final DisplayContent dc = defaultTaskDisplayArea.getDisplayContent();
- final TaskDisplayArea secondTaskDisplayArea = new TaskDisplayArea(dc,
- mRootWindowContainer.mWmService, "SecondaryTaskDisplayArea", FEATURE_VENDOR_FIRST);
- // Add second display area right above the default one
- defaultTaskDisplayArea.getParent().addChild(secondTaskDisplayArea,
- defaultTaskDisplayArea.getParent().mChildren.indexOf(defaultTaskDisplayArea) + 1);
+ final TaskDisplayArea secondTaskDisplayArea = WindowTestsBase.createTaskDisplayArea(dc,
+ mRootWindowContainer.mWmService, "TestTaskDisplayArea", FEATURE_VENDOR_FIRST);
final ActivityStack secondStack = secondTaskDisplayArea.createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
new ActivityBuilder(mService).setCreateTask(true).setStack(secondStack)
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 3c98272..a979c86 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -269,6 +269,8 @@
rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
mActivity.mDisplayContent.mInputMethodTarget = addWindowToActivity(mActivity);
+ mActivity.mDisplayContent.mInputMethodInputTarget =
+ mActivity.mDisplayContent.mInputMethodTarget;
// Because the aspect ratio of display doesn't exceed the max aspect ratio of activity.
// The activity should still fill its parent container and IME can attach to the activity.
assertTrue(mActivity.matchParentBounds());
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
index be9bf24..2233b22 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -33,7 +34,7 @@
import android.animation.ValueAnimator;
import android.graphics.Matrix;
import android.graphics.Point;
-import android.hardware.power.Boost;
+import android.os.Handler;
import android.os.PowerManagerInternal;
import android.platform.test.annotations.Presubmit;
import android.view.Choreographer;
@@ -46,11 +47,12 @@
import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
+import com.android.server.AnimationThread;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -64,8 +66,7 @@
*/
@SmallTest
@Presubmit
-@RunWith(WindowTestRunner.class)
-public class SurfaceAnimationRunnerTest extends WindowTestsBase {
+public class SurfaceAnimationRunnerTest {
@Mock SurfaceControl mMockSurface;
@Mock Transaction mMockTransaction;
@@ -75,6 +76,9 @@
private SurfaceAnimationRunner mSurfaceAnimationRunner;
private CountDownLatch mFinishCallbackLatch;
+ private final Handler mAnimationThreadHandler = AnimationThread.getHandler();
+ private final Handler mSurfaceAnimationHandler = SurfaceAnimationThread.getHandler();
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -84,6 +88,12 @@
mMockTransaction, mMockPowerManager);
}
+ @After
+ public void tearDown() {
+ SurfaceAnimationThread.dispose();
+ AnimationThread.dispose();
+ }
+
private void finishedCallback() {
mFinishCallbackLatch.countDown();
}
@@ -101,8 +111,7 @@
verify(mMockTransaction, atLeastOnce()).setMatrix(eq(mMockSurface), eq(m), any());
verify(mMockTransaction, atLeastOnce()).setAlpha(eq(mMockSurface), eq(1.0f));
- waitHandlerIdle(SurfaceAnimationThread.getHandler());
- mFinishCallbackLatch.await(1, SECONDS);
+ waitHandlerIdle(mSurfaceAnimationHandler);
assertFinishCallbackCalled();
m.setTranslate(10, 0);
@@ -120,7 +129,7 @@
.startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction,
this::finishedCallback);
mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
- waitUntilHandlersIdle();
+ waitHandlerIdle(mAnimationThreadHandler);
assertTrue(mSurfaceAnimationRunner.mPendingAnimations.isEmpty());
assertFinishCallbackNotCalled();
}
@@ -135,7 +144,7 @@
assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface);
assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
- waitUntilHandlersIdle();
+ waitHandlerIdle(mAnimationThreadHandler);
assertFinishCallbackNotCalled();
}
@@ -180,9 +189,8 @@
assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
mSurfaceAnimationRunner.continueStartingAnimations();
waitUntilNextFrame();
- waitHandlerIdle(SurfaceAnimationThread.getHandler());
+ waitHandlerIdle(mSurfaceAnimationHandler);
assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty());
- mFinishCallbackLatch.await(1, SECONDS);
assertFinishCallbackCalled();
}
@@ -194,7 +202,7 @@
mMockTransaction, this::finishedCallback);
waitUntilNextFrame();
- verify(mMockPowerManager).setPowerBoost(eq(Boost.INTERACTION), eq(0));
+ verify(mMockPowerManager).powerHint(anyInt(), eq(0));
}
private void waitUntilNextFrame() throws Exception {
@@ -204,7 +212,15 @@
latch.await();
}
+ private static void waitHandlerIdle(Handler handler) {
+ handler.runWithScissors(() -> { }, 0 /* timeout */);
+ }
+
private void assertFinishCallbackCalled() {
+ try {
+ assertTrue(mFinishCallbackLatch.await(5, SECONDS));
+ } catch (InterruptedException ignored) {
+ }
assertEquals(0, mFinishCallbackLatch.getCount());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 218261b..50675b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -102,6 +102,7 @@
private static final int[] TEST_USER_PROFILE_IDS = {};
+ private Description mDescription;
private Context mContext;
private StaticMockitoSession mMockitoSession;
private ActivityManagerService mAmService;
@@ -121,6 +122,7 @@
return new Statement() {
@Override
public void evaluate() throws Throwable {
+ mDescription = description;
Throwable throwable = null;
try {
runWithDexmakerShareClassLoader(SystemServicesTestRule.this::setUp);
@@ -373,6 +375,10 @@
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
}
+ Description getDescription() {
+ return mDescription;
+ }
+
WindowManagerService getWindowManagerService() {
return mWmService;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 0c4bb9f..0b99e32 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -183,6 +183,7 @@
}
}
+ @UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testPrepareTaskSnapshot() {
mAppWindow.mWinAnimator.mLastAlpha = 1f;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index b8a1c2b..bce1142 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -74,6 +74,7 @@
private IActivityManager mService;
private ITaskStackListener mTaskStackListener;
+ private static final int WAIT_TIMEOUT_MS = 5000;
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static boolean sTaskStackChangedCalled;
@@ -490,7 +491,8 @@
SystemUtil.runWithShellPermissionIdentity(() -> context.startActivity(
new Intent(context, activityClass).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
options.toBundle()));
- final TestActivity activity = (TestActivity) monitor.waitForActivityWithTimeout(1000);
+ final TestActivity activity =
+ (TestActivity) monitor.waitForActivityWithTimeout(WAIT_TIMEOUT_MS);
if (activity == null) {
throw new RuntimeException("Timed out waiting for Activity");
}
@@ -508,7 +510,7 @@
private void waitForCallback(CountDownLatch latch) {
try {
- final boolean result = latch.await(4, TimeUnit.SECONDS);
+ final boolean result = latch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
if (!result) {
throw new RuntimeException("Timed out waiting for task stack change notification");
}
@@ -560,7 +562,7 @@
if (mIsResumed == isResumed) {
return;
}
- wait(5000);
+ wait(WAIT_TIMEOUT_MS);
}
assertEquals("The activity resume state change timed out", isResumed, mIsResumed);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 53ede60..573e37a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -270,6 +270,7 @@
assertEquals(WINDOWING_MODE_FULLSCREEN, token.getWindowingMode());
}
+ @UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testFixedRotationRecentsAnimatingTask() {
final RecentsAnimationController recentsController = mock(RecentsAnimationController.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 3c0dd1e..47e4559 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -46,6 +46,7 @@
@RunWith(WindowTestRunner.class)
public class WindowContainerTraversalTests extends WindowTestsBase {
+ @UseTestDisplay(addWindows = { W_DOCK_DIVIDER, W_INPUT_METHOD })
@Test
public void testDockedDividerPosition() {
final WindowState splitScreenWindow = createWindowOnStack(null,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 1266c50..8fa3a12 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -145,10 +145,9 @@
@Test
public void testTaskFocusChange_stackHomeTypeWithDifferentTaskDisplayArea_focusChanges()
throws RemoteException {
- DisplayContent display = createNewDisplay();
- TaskDisplayArea secondTda =
- new TaskDisplayArea(display, mWm, "Tapped TDA", FEATURE_VENDOR_FIRST);
- display.addChild(secondTda, 1);
+ final DisplayContent display = createNewDisplay();
+ final TaskDisplayArea secondTda = createTaskDisplayArea(
+ display, mWm, "Tapped TDA", FEATURE_VENDOR_FIRST);
// Current focused window
ActivityStack focusedStack = createTaskStackOnDisplay(
WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, display);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 04d52af..7cc19ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -521,6 +521,7 @@
assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType);
}
+ @UseTestDisplay
@Test
public void testTaskInfoCallback() {
final ArrayList<RunningTaskInfo> lastReportedTiles = new ArrayList<>();
@@ -584,6 +585,7 @@
assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType);
}
+ @UseTestDisplay
@Test
public void testHierarchyTransaction() {
final ArrayMap<IBinder, RunningTaskInfo> lastReportedTiles = new ArrayMap<>();
@@ -669,6 +671,14 @@
assertEquals(1, lastReportedTiles.size());
assertEquals(ACTIVITY_TYPE_HOME,
lastReportedTiles.get(info1.token.asBinder()).topActivityType);
+
+ // This just needs to not crash (ie. it should be possible to reparent to display twice)
+ wct = new WindowContainerTransaction();
+ wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+ wct = new WindowContainerTransaction();
+ wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
}
private List<Task> getTasksCreatedByOrganizer(DisplayContent dc) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 360d73b..ce9dd68 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -36,7 +36,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -417,10 +416,11 @@
assertFalse(app.canAffectSystemUiFlags());
}
+ @UseTestDisplay(addWindows = { W_ACTIVITY, W_STATUS_BAR })
@Test
public void testVisibleWithInsetsProvider() {
- final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState statusBar = mStatusBarWindow;
+ final WindowState app = mAppWindow;
statusBar.mHasSurface = true;
assertTrue(statusBar.isVisible());
mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR)
@@ -542,6 +542,7 @@
assertTrue(window.isVisibleByPolicy());
}
+ @UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE })
@Test
public void testRequestDrawIfNeeded() {
final WindowState startingApp = createWindow(null /* parent */,
@@ -567,6 +568,7 @@
assertEquals(Arrays.asList(keyguardHostWindow, startingWindow), outWaitingForDrawn);
}
+ @UseTestDisplay(addWindows = W_ABOVE_ACTIVITY)
@Test
public void testReportResizedWithRemoteException() {
final WindowState win = mChildAppWindowAbove;
@@ -597,6 +599,7 @@
assertFalse(win.getOrientationChanging());
}
+ @UseTestDisplay(addWindows = W_ABOVE_ACTIVITY)
@Test
public void testRequestResizeForBlastSync() {
final WindowState win = mChildAppWindowAbove;
@@ -677,6 +680,7 @@
assertTrue(win0.cantReceiveTouchInput());
}
+ @UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testNeedsRelativeLayeringToIme_notAttached() {
WindowState sameTokenWindow = createWindow(null, TYPE_BASE_APPLICATION, mAppWindow.mToken,
@@ -689,6 +693,7 @@
assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
}
+ @UseTestDisplay(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testNeedsRelativeLayeringToIme_startingWindow() {
WindowState sameTokenWindow = createWindow(null, TYPE_APPLICATION_STARTING,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 156298c..0bbe0a0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -39,11 +39,11 @@
import static org.mockito.Mockito.mock;
-import android.content.Context;
+import android.annotation.IntDef;
import android.content.Intent;
+import android.hardware.display.DisplayManager;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.util.Log;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.IDisplayWindowInsetsController;
@@ -54,23 +54,36 @@
import android.view.View;
import android.view.WindowManager;
+import com.android.internal.util.ArrayUtils;
import com.android.server.AttributeCache;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.runner.Description;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
/** Common base class for window manager unit test classes. */
class WindowTestsBase extends SystemServiceTestsBase {
- private static final String TAG = WindowTestsBase.class.getSimpleName();
WindowManagerService mWm;
private final IWindow mIWindow = new TestIWindow();
private Session mMockSession;
static int sNextStackId = 1000;
- /** Non-default display. */
- DisplayContent mDisplayContent;
DisplayInfo mDisplayInfo = new DisplayInfo();
+ DisplayContent mDefaultDisplay;
+
+ /**
+ * It is {@link #mDefaultDisplay} by default. If the test class or method is annotated with
+ * {@link UseTestDisplay}, it will be an additional display.
+ */
+ DisplayContent mDisplayContent;
+
+ // The following fields are only available depending on the usage of annotation UseTestDisplay.
WindowState mWallpaperWindow;
WindowState mImeWindow;
WindowState mImeDialogWindow;
@@ -97,48 +110,82 @@
mWm = mSystemServicesTestRule.getWindowManagerService();
SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock);
+ mDefaultDisplay = mWm.mRoot.getDefaultDisplay();
mTransaction = mSystemServicesTestRule.mTransaction;
mMockSession = mock(Session.class);
- final Context context = getInstrumentation().getTargetContext();
- // If @Before throws an exception, the error isn't logged. This will make sure any failures
- // in the set up are clear. This can be removed when b/37850063 is fixed.
- try {
- beforeCreateDisplay();
- context.getDisplay().getDisplayInfo(mDisplayInfo);
- mDisplayContent = createNewDisplay(true /* supportIme */);
+ getInstrumentation().getTargetContext().getSystemService(DisplayManager.class)
+ .getDisplay(Display.DEFAULT_DISPLAY).getDisplayInfo(mDisplayInfo);
- // Set-up some common windows.
- mWallpaperWindow = createCommonWindow(null, TYPE_WALLPAPER, "wallpaperWindow");
- mImeWindow = createCommonWindow(null, TYPE_INPUT_METHOD, "mImeWindow");
- mDisplayContent.mInputMethodWindow = mImeWindow;
- mImeDialogWindow = createCommonWindow(null, TYPE_INPUT_METHOD_DIALOG,
- "mImeDialogWindow");
- mStatusBarWindow = createCommonWindow(null, TYPE_STATUS_BAR, "mStatusBarWindow");
- mNotificationShadeWindow = createCommonWindow(null, TYPE_NOTIFICATION_SHADE,
- "mNotificationShadeWindow");
- mNavBarWindow = createCommonWindow(null, TYPE_NAVIGATION_BAR, "mNavBarWindow");
- mDockedDividerWindow = createCommonWindow(null, TYPE_DOCK_DIVIDER,
- "mDockedDividerWindow");
- mAppWindow = createCommonWindow(null, TYPE_BASE_APPLICATION, "mAppWindow");
- mChildAppWindowAbove = createCommonWindow(mAppWindow,
- TYPE_APPLICATION_ATTACHED_DIALOG,
- "mChildAppWindowAbove");
- mChildAppWindowBelow = createCommonWindow(mAppWindow,
- TYPE_APPLICATION_MEDIA_OVERLAY,
- "mChildAppWindowBelow");
- mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(false);
-
- // Adding a display will cause freezing the display. Make sure to wait until it's
- // unfrozen to not run into race conditions with the tests.
- waitUntilHandlersIdle();
- } catch (Exception e) {
- Log.e(TAG, "Failed to set up test", e);
- throw e;
+ // Only create an additional test display for annotated test class/method because it may
+ // significantly increase the execution time.
+ final Description description = mSystemServicesTestRule.getDescription();
+ UseTestDisplay testDisplayAnnotation = description.getAnnotation(UseTestDisplay.class);
+ if (testDisplayAnnotation == null) {
+ testDisplayAnnotation = description.getTestClass().getAnnotation(UseTestDisplay.class);
+ }
+ if (testDisplayAnnotation != null) {
+ createTestDisplay(testDisplayAnnotation);
+ } else {
+ mDisplayContent = mDefaultDisplay;
}
}
- void beforeCreateDisplay() {
+ private void createTestDisplay(UseTestDisplay annotation) {
+ beforeCreateTestDisplay();
+ mDisplayContent = createNewDisplay(true /* supportIme */);
+
+ final boolean addAll = annotation.addAllCommonWindows();
+ final @CommonTypes int[] requestedWindows = annotation.addWindows();
+
+ if (addAll || ArrayUtils.contains(requestedWindows, W_WALLPAPER)) {
+ mWallpaperWindow = createCommonWindow(null, TYPE_WALLPAPER, "wallpaperWindow");
+ }
+ if (addAll || ArrayUtils.contains(requestedWindows, W_INPUT_METHOD)) {
+ mImeWindow = createCommonWindow(null, TYPE_INPUT_METHOD, "mImeWindow");
+ mDisplayContent.mInputMethodWindow = mImeWindow;
+ }
+ if (addAll || ArrayUtils.contains(requestedWindows, W_INPUT_METHOD_DIALOG)) {
+ mImeDialogWindow = createCommonWindow(null, TYPE_INPUT_METHOD_DIALOG,
+ "mImeDialogWindow");
+ }
+ if (addAll || ArrayUtils.contains(requestedWindows, W_STATUS_BAR)) {
+ mStatusBarWindow = createCommonWindow(null, TYPE_STATUS_BAR, "mStatusBarWindow");
+ }
+ if (addAll || ArrayUtils.contains(requestedWindows, W_NOTIFICATION_SHADE)) {
+ mNotificationShadeWindow = createCommonWindow(null, TYPE_NOTIFICATION_SHADE,
+ "mNotificationShadeWindow");
+ }
+ if (addAll || ArrayUtils.contains(requestedWindows, W_NAVIGATION_BAR)) {
+ mNavBarWindow = createCommonWindow(null, TYPE_NAVIGATION_BAR, "mNavBarWindow");
+ }
+ if (addAll || ArrayUtils.contains(requestedWindows, W_DOCK_DIVIDER)) {
+ mDockedDividerWindow = createCommonWindow(null, TYPE_DOCK_DIVIDER,
+ "mDockedDividerWindow");
+ }
+ final boolean addAboveApp = ArrayUtils.contains(requestedWindows, W_ABOVE_ACTIVITY);
+ final boolean addBelowApp = ArrayUtils.contains(requestedWindows, W_BELOW_ACTIVITY);
+ if (addAll || addAboveApp || addBelowApp
+ || ArrayUtils.contains(requestedWindows, W_ACTIVITY)) {
+ mAppWindow = createCommonWindow(null, TYPE_BASE_APPLICATION, "mAppWindow");
+ }
+ if (addAll || addAboveApp) {
+ mChildAppWindowAbove = createCommonWindow(mAppWindow, TYPE_APPLICATION_ATTACHED_DIALOG,
+ "mChildAppWindowAbove");
+ }
+ if (addAll || addBelowApp) {
+ mChildAppWindowBelow = createCommonWindow(mAppWindow, TYPE_APPLICATION_MEDIA_OVERLAY,
+ "mChildAppWindowBelow");
+ }
+
+ mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(false);
+
+ // Adding a display will cause freezing the display. Make sure to wait until it's
+ // unfrozen to not run into race conditions with the tests.
+ waitUntilHandlersIdle();
+ }
+
+ void beforeCreateTestDisplay() {
// Called before display is created.
}
@@ -261,6 +308,20 @@
}
}
+ /** Creates a {@link TaskDisplayArea} right above the default one. */
+ static TaskDisplayArea createTaskDisplayArea(DisplayContent displayContent,
+ WindowManagerService service, String name, int displayAreaFeature) {
+ final TaskDisplayArea newTaskDisplayArea = new TaskDisplayArea(
+ displayContent, service, name, displayAreaFeature);
+ final TaskDisplayArea defaultTaskDisplayArea = displayContent.getDefaultTaskDisplayArea();
+
+ // Insert the new TDA to the correct position.
+ defaultTaskDisplayArea.getParent().addChild(newTaskDisplayArea,
+ defaultTaskDisplayArea.getParent().mChildren.indexOf(defaultTaskDisplayArea)
+ + 1);
+ return newTaskDisplayArea;
+ }
+
/** Creates a {@link ActivityStack} and adds it to the specified {@link DisplayContent}. */
ActivityStack createTaskStackOnDisplay(DisplayContent dc) {
return createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, dc);
@@ -378,4 +439,66 @@
void removeGlobalMinSizeRestriction() {
mWm.mAtmService.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp = 1;
}
+
+ /**
+ * Avoids rotating screen disturbed by some conditions. It is usually used for the default
+ * display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions).
+ *
+ * @see DisplayRotation#updateRotationUnchecked
+ */
+ void unblockDisplayRotation(DisplayContent dc) {
+ mWm.stopFreezingDisplayLocked();
+ // The rotation animation won't actually play, it needs to be cleared manually.
+ dc.setRotationAnimation(null);
+ }
+
+ // The window definition for UseTestDisplay#addWindows. The test can declare to add only
+ // necessary windows, that avoids adding unnecessary overhead of unused windows.
+ static final int W_NOTIFICATION_SHADE = TYPE_NOTIFICATION_SHADE;
+ static final int W_STATUS_BAR = TYPE_STATUS_BAR;
+ static final int W_NAVIGATION_BAR = TYPE_NAVIGATION_BAR;
+ static final int W_INPUT_METHOD_DIALOG = TYPE_INPUT_METHOD_DIALOG;
+ static final int W_INPUT_METHOD = TYPE_INPUT_METHOD;
+ static final int W_DOCK_DIVIDER = TYPE_DOCK_DIVIDER;
+ static final int W_ABOVE_ACTIVITY = TYPE_APPLICATION_ATTACHED_DIALOG;
+ static final int W_ACTIVITY = TYPE_BASE_APPLICATION;
+ static final int W_BELOW_ACTIVITY = TYPE_APPLICATION_MEDIA_OVERLAY;
+ static final int W_WALLPAPER = TYPE_WALLPAPER;
+
+ /** The common window types supported by {@link UseTestDisplay}. */
+ @Retention(RetentionPolicy.RUNTIME)
+ @IntDef(value = {
+ W_NOTIFICATION_SHADE,
+ W_STATUS_BAR,
+ W_NAVIGATION_BAR,
+ W_INPUT_METHOD_DIALOG,
+ W_INPUT_METHOD,
+ W_DOCK_DIVIDER,
+ W_ABOVE_ACTIVITY,
+ W_ACTIVITY,
+ W_BELOW_ACTIVITY,
+ W_WALLPAPER,
+ })
+ @interface CommonTypes {
+ }
+
+ /**
+ * The annotation for class and method (higher priority) to create a non-default display that
+ * will be assigned to {@link #mDisplayContent}. It is used if the test needs
+ * <ul>
+ * <li>Pure empty display.</li>
+ * <li>Configured common windows.</li>
+ * <li>Independent and customizable orientation.</li>
+ * <li>Cross display operation.</li>
+ * </ul>
+ *
+ * @see TestDisplayContent
+ * @see #createTestDisplay
+ **/
+ @Target({ ElementType.METHOD, ElementType.TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface UseTestDisplay {
+ boolean addAllCommonWindows() default false;
+ @CommonTypes int[] addWindows() default {};
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index 5264e9a..cef202c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -142,6 +142,7 @@
assertEquals(0, token.getWindowsCount());
}
+ @UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER })
@Test
public void testFinishFixedRotationTransform() {
final WindowToken appToken = mAppWindow.mToken;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index e6b4e0f..dfb7280 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -62,6 +62,7 @@
*/
@SmallTest
@Presubmit
+@WindowTestsBase.UseTestDisplay(addAllCommonWindows = true)
@RunWith(WindowTestRunner.class)
public class ZOrderingTests extends WindowTestsBase {
@@ -152,7 +153,7 @@
private LayerRecordingTransaction mTransaction;
@Override
- void beforeCreateDisplay() {
+ void beforeCreateTestDisplay() {
// We can't use @Before here because it may happen after WindowTestsBase @Before
// which is after construction of the DisplayContent, meaning the HierarchyRecorder
// would miss construction of the top-level layers.
diff --git a/telephony/api/system-current.txt b/telephony/api/system-current.txt
index 4b425bd..ef94c76 100644
--- a/telephony/api/system-current.txt
+++ b/telephony/api/system-current.txt
@@ -1718,7 +1718,6 @@
ctor @Deprecated public MmTelFeature.MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities);
ctor public MmTelFeature.MmTelCapabilities(int);
method public final void addCapabilities(int);
- method public final boolean isCapable(int);
method public final void removeCapabilities(int);
}
diff --git a/telephony/api/system-removed.txt b/telephony/api/system-removed.txt
index c7fd304..ae46075 100644
--- a/telephony/api/system-removed.txt
+++ b/telephony/api/system-removed.txt
@@ -1,11 +1,6 @@
// Signature format: 2.0
package android.telephony {
- public final class PreciseDataConnectionState implements android.os.Parcelable {
- method @Deprecated @Nullable public android.net.LinkProperties getDataConnectionLinkProperties();
- method @Deprecated public int getDataConnectionNetworkType();
- }
-
public class TelephonyManager {
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void answerRingingCall();
method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public boolean endCall();
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index bc987a6..71a1964 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -623,6 +623,10 @@
}
private static int getCarrierPrivilegeStatus(Context context, int subId, int uid) {
+ if (uid == Process.SYSTEM_UID || uid == Process.PHONE_UID) {
+ // Skip the check if it's one of these special uids
+ return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+ }
final long identity = Binder.clearCallingIdentity();
try {
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 157e885..b617f6a 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2549,15 +2549,15 @@
/**
* List of 4 customized 5G SS reference signal received quality (SSRSRQ) thresholds.
* <p>
- * Reference: 3GPP TS 38.215
+ * Reference: 3GPP TS 38.215; 3GPP TS 38.133 section 10
* <p>
- * 4 threshold integers must be within the boundaries [-20 dB, -3 dB], and the levels are:
+ * 4 threshold integers must be within the boundaries [-43 dB, 20 dB], and the levels are:
* <UL>
- * <LI>"NONE: [-20, threshold1]"</LI>
+ * <LI>"NONE: [-43, threshold1]"</LI>
* <LI>"POOR: (threshold1, threshold2]"</LI>
* <LI>"MODERATE: (threshold2, threshold3]"</LI>
* <LI>"GOOD: (threshold3, threshold4]"</LI>
- * <LI>"EXCELLENT: (threshold4, -3]"</LI>
+ * <LI>"EXCELLENT: (threshold4, 20]"</LI>
* </UL>
* <p>
* This key is considered invalid if the format is violated. If the key is invalid or
@@ -3216,6 +3216,17 @@
"5g_icon_display_secondary_grace_period_string";
/**
+ * Whether device reset all of NR timers when device camped on a network that haven't 5G
+ * capability and RRC currently in IDLE state.
+ *
+ * The default value is false;
+ *
+ * @hide
+ */
+ public static final String KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL =
+ "nr_timers_reset_if_non_endc_and_rrc_idle_bool";
+
+ /**
* Controls time in milliseconds until DcTracker reevaluates 5G connection state.
* @hide
*/
@@ -4284,12 +4295,12 @@
-65, /* SIGNAL_STRENGTH_GREAT */
});
sDefaults.putIntArray(KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY,
- // Boundaries: [-20 dB, -3 dB]
+ // Boundaries: [-43 dB, 20 dB]
new int[] {
- -16, /* SIGNAL_STRENGTH_POOR */
- -12, /* SIGNAL_STRENGTH_MODERATE */
- -9, /* SIGNAL_STRENGTH_GOOD */
- -6 /* SIGNAL_STRENGTH_GREAT */
+ -31, /* SIGNAL_STRENGTH_POOR */
+ -19, /* SIGNAL_STRENGTH_MODERATE */
+ -7, /* SIGNAL_STRENGTH_GOOD */
+ 6 /* SIGNAL_STRENGTH_GREAT */
});
sDefaults.putIntArray(KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY,
// Boundaries: [-23 dB, 40 dB]
@@ -4326,6 +4337,7 @@
+ "not_restricted_rrc_con:5G");
sDefaults.putString(KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, "");
sDefaults.putString(KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, "");
+ sDefaults.putBoolean(KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL, false);
/* Default value is 1 hour. */
sDefaults.putLong(KEY_5G_WATCHDOG_TIME_MS_LONG, 3600000);
sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_BOOL, false);
diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java
index 4e4abd0..aa12301 100644
--- a/telephony/java/android/telephony/CellLocation.java
+++ b/telephony/java/android/telephony/CellLocation.java
@@ -46,7 +46,14 @@
*
* Callers wishing to request a single location update should use
* {@link TelephonyManager#requestCellInfoUpdate}.
+ *
+ * @deprecated this method has undesirable side-effects, and it calls into the OS without
+ * access to a {@link android.content.Context Context}, meaning that certain safety checks and
+ * attribution are error-prone. Given that this method has numerous downsides, and given that
+ * there are long-available superior alternatives, callers are strongly discouraged from using
+ * this method.
*/
+ @Deprecated
public static void requestLocationUpdate() {
// Since this object doesn't have a context, this is the best we can do.
final Context appContext = ActivityThread.currentApplication();
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index 95fe90a..8e50bba 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -54,12 +54,12 @@
};
// Lifted from Default carrier configs and max range of SSRSRQ
- // Boundaries: [-20 dB, -3 dB]
+ // Boundaries: [-43 dB, 20 dB]
private int[] mSsRsrqThresholds = new int[] {
- -16, /* SIGNAL_STRENGTH_POOR */
- -12, /* SIGNAL_STRENGTH_MODERATE */
- -9, /* SIGNAL_STRENGTH_GOOD */
- -6 /* SIGNAL_STRENGTH_GREAT */
+ -31, /* SIGNAL_STRENGTH_POOR */
+ -19, /* SIGNAL_STRENGTH_MODERATE */
+ -7, /* SIGNAL_STRENGTH_GOOD */
+ 6 /* SIGNAL_STRENGTH_GREAT */
};
// Lifted from Default carrier configs and max range of SSSINR
@@ -183,8 +183,8 @@
}
/**
- * Reference: 3GPP TS 38.215.
- * Range: -20 dB to -3 dB.
+ * Reference: 3GPP TS 38.215; 3GPP TS 38.133 section 10
+ * Range: -43 dB to 20 dB.
* @return SS reference signal received quality, {@link CellInfo#UNAVAILABLE} means unreported
* value.
*/
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 31a83c9..e0b5779 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -572,7 +572,8 @@
return "Unknown reg state " + registrationState;
}
- private static String nrStateToString(@NRState int nrState) {
+ /** @hide */
+ public static String nrStateToString(@NRState int nrState) {
switch (nrState) {
case NR_STATE_RESTRICTED:
return "RESTRICTED";
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index b682bdd..8660e38 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -169,19 +169,6 @@
/**
* Returns the network type associated with this data connection.
*
- * @deprecated use {@link getNetworkType()}
- * @hide
- * @removed Removed from the R preview SDK but was never part of the stable API surface.
- */
- @Deprecated
- @SystemApi
- public @NetworkType int getDataConnectionNetworkType() {
- return mNetworkType;
- }
-
- /**
- * Returns the network type associated with this data connection.
- *
* Return the current/latest (radio) bearer technology that carries this data connection.
* For a variety of reasons, the network type can change during the life of the data
* connection, and this information is not reliable unless the physical link is currently
@@ -220,20 +207,6 @@
/**
* Get the properties of the network link {@link LinkProperties}.
- *
- * @deprecated use {@link #getLinkProperties()}
- * @hide
- * @removed Removed from the R preview SDK but was never part of the stable API surface.
- */
- @Deprecated
- @SystemApi
- @Nullable
- public LinkProperties getDataConnectionLinkProperties() {
- return mLinkProperties;
- }
-
- /**
- * Get the properties of the network link {@link LinkProperties}.
*/
@Nullable
public LinkProperties getLinkProperties() {
diff --git a/telephony/java/android/telephony/SmsCbEtwsInfo.java b/telephony/java/android/telephony/SmsCbEtwsInfo.java
index 2a7f7ad..a98916d 100644
--- a/telephony/java/android/telephony/SmsCbEtwsInfo.java
+++ b/telephony/java/android/telephony/SmsCbEtwsInfo.java
@@ -27,6 +27,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.DateTimeException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
@@ -173,7 +174,7 @@
/**
* Returns the Warning-Security-Information timestamp (GSM primary notifications only).
* As of Release 10, 3GPP TS 23.041 states that the UE shall ignore this value if received.
- * @return a UTC timestamp in System.currentTimeMillis() format, or 0 if not present
+ * @return a UTC timestamp in System.currentTimeMillis() format, or 0 if not present or invalid.
*/
public long getPrimaryNotificationTimestamp() {
if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 7) {
@@ -201,18 +202,23 @@
// timezoneOffset is in quarter hours.
int timeZoneOffsetSeconds = timezoneOffset * 15 * 60;
- LocalDateTime localDateTime = LocalDateTime.of(
- // We only need to support years above 2000.
- year + 2000,
- month /* 1-12 */,
- day,
- hour,
- minute,
- second);
+ try {
+ LocalDateTime localDateTime = LocalDateTime.of(
+ // We only need to support years above 2000.
+ year + 2000,
+ month /* 1-12 */,
+ day,
+ hour,
+ minute,
+ second);
- long epochSeconds = localDateTime.toEpochSecond(ZoneOffset.UTC) - timeZoneOffsetSeconds;
- // Convert to milliseconds, ignore overflow.
- return epochSeconds * 1000;
+ long epochSeconds = localDateTime.toEpochSecond(ZoneOffset.UTC) - timeZoneOffsetSeconds;
+ // Convert to milliseconds, ignore overflow.
+ return epochSeconds * 1000;
+ } catch (DateTimeException ex) {
+ // No-op
+ }
+ return 0;
}
/**
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index b376660..183fdcc 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -670,10 +670,12 @@
}
if (priority < 0x00 || priority > 0x03) {
+ Log.e(TAG, "Invalid Priority " + priority);
priority = SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
}
if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
+ Log.e(TAG, "Invalid Validity Period " + validityPeriod);
validityPeriod = SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
}
@@ -1231,10 +1233,12 @@
}
if (priority < 0x00 || priority > 0x03) {
+ Log.e(TAG, "Invalid Priority " + priority);
priority = SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
}
if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
+ Log.e(TAG, "Invalid Validity Period " + validityPeriod);
validityPeriod = SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index 347dcc8..717a9b1 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -255,28 +255,6 @@
}
/**
- * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
- * +CMT unsolicited response (PDU mode, of course)
- * +CMT: [<alpha>],<length><CR><LF><pdu>
- *
- * Only public for debugging and for RIL
- *
- * {@hide}
- */
- public static SmsMessage newFromCMT(byte[] pdu) {
- // received SMS in 3GPP format
- SmsMessageBase wrappedMessage =
- com.android.internal.telephony.gsm.SmsMessage.newFromCMT(pdu);
-
- if (wrappedMessage != null) {
- return new SmsMessage(wrappedMessage);
- } else {
- Rlog.e(LOG_TAG, "newFromCMT(): wrappedMessage is null");
- return null;
- }
- }
-
- /**
* Creates an SmsMessage from an SMS EF record.
*
* @param index Index of SMS EF record.
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 01d468c..de0fb86 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -285,8 +285,8 @@
public static final int CAPABILITY_TYPE_SMS = 1 << 3;
/**
- * @hide
- */
+ * @hide
+ */
@Override
@SystemApi @TestApi
public final void addCapabilities(@MmTelCapability int capabilities) {
@@ -294,8 +294,8 @@
}
/**
- * @hide
- */
+ * @hide
+ */
@Override
@SystemApi @TestApi
public final void removeCapabilities(@MmTelCapability int capability) {
@@ -303,17 +303,18 @@
}
/**
- * @hide
- */
+ * @param capabilities a bitmask of one or more {@link MmTelCapability}.
+ *
+ * @return true if all queried capabilities are true, otherwise false.
+ */
@Override
- @SystemApi @TestApi
public final boolean isCapable(@MmTelCapability int capabilities) {
return super.isCapable(capabilities);
}
/**
- * @hide
- */
+ * @hide
+ */
@NonNull
@Override
public String toString() {
diff --git a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
index 3bd8cdd..f8ab87d 100644
--- a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
+++ b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
@@ -65,13 +65,7 @@
return "";
}
- if (!mIs7BitTranslationTableLoaded) {
- mTranslationTableCommon = new SparseIntArray();
- mTranslationTableGSM = new SparseIntArray();
- mTranslationTableCDMA = new SparseIntArray();
- load7BitTranslationTableFromXml();
- mIs7BitTranslationTableLoaded = true;
- }
+ ensure7BitTranslationTableLoaded();
if ((mTranslationTableCommon != null && mTranslationTableCommon.size() > 0) ||
(mTranslationTableGSM != null && mTranslationTableGSM.size() > 0) ||
@@ -115,6 +109,8 @@
*/
int translation = -1;
+ ensure7BitTranslationTableLoaded();
+
if (mTranslationTableCommon != null) {
translation = mTranslationTableCommon.get(c, -1);
}
@@ -155,6 +151,18 @@
}
}
+ private static void ensure7BitTranslationTableLoaded() {
+ synchronized (Sms7BitEncodingTranslator.class) {
+ if (!mIs7BitTranslationTableLoaded) {
+ mTranslationTableCommon = new SparseIntArray();
+ mTranslationTableGSM = new SparseIntArray();
+ mTranslationTableCDMA = new SparseIntArray();
+ load7BitTranslationTableFromXml();
+ mIs7BitTranslationTableLoaded = true;
+ }
+ }
+ }
+
/**
* Load the whole translation table file from the framework resource
* encoded in XML.
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
index 4fbafb7..d186fcf 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -35,6 +35,7 @@
import com.android.telephony.Rlog;
import java.io.ByteArrayOutputStream;
+import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -315,10 +316,16 @@
}
public long toMillis() {
- LocalDateTime localDateTime =
- LocalDateTime.of(year, monthOrdinal, monthDay, hour, minute, second);
- Instant instant = localDateTime.toInstant(mZoneId.getRules().getOffset(localDateTime));
- return instant.toEpochMilli();
+ try {
+ LocalDateTime localDateTime =
+ LocalDateTime.of(year, monthOrdinal, monthDay, hour, minute, second);
+ Instant instant =
+ localDateTime.toInstant(mZoneId.getRules().getOffset(localDateTime));
+ return instant.toEpochMilli();
+ } catch (DateTimeException ex) {
+ Rlog.e(LOG_TAG, "Invalid timestamp", ex);
+ }
+ return 0;
}
@@ -1093,7 +1100,7 @@
bData.hasUserDataHeader = (inStream.read(1) == 1);
inStream.skip(3);
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1462,7 +1469,7 @@
bData.reportReq = (inStream.read(1) == 1);
inStream.skip(4);
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "REPLY_OPTION decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1481,7 +1488,7 @@
decodeSuccess = true;
bData.numberOfMessages = IccUtils.cdmaBcdByteToInt((byte)inStream.read(8));
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "NUMBER_OF_MESSAGES decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1500,7 +1507,7 @@
decodeSuccess = true;
bData.depositIndex = (inStream.read(8) << 8) | inStream.read(8);
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "MESSAGE_DEPOSIT_INDEX decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1587,7 +1594,7 @@
bData.errorClass = inStream.read(2);
bData.messageStatus = inStream.read(6);
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "MESSAGE_STATUS decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1607,7 +1614,7 @@
decodeSuccess = true;
bData.msgCenterTimeStamp = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8));
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "MESSAGE_CENTER_TIME_STAMP decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1626,7 +1633,7 @@
decodeSuccess = true;
bData.validityPeriodAbsolute = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8));
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "VALIDITY_PERIOD_ABSOLUTE decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1646,7 +1653,7 @@
bData.deferredDeliveryTimeAbsolute = TimeStamp.fromByteArray(
inStream.readByteArray(6 * 8));
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_ABSOLUTE decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1665,7 +1672,7 @@
decodeSuccess = true;
bData.deferredDeliveryTimeRelative = inStream.read(8);
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "VALIDITY_PERIOD_RELATIVE decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1685,7 +1692,7 @@
decodeSuccess = true;
bData.validityPeriodRelative = inStream.read(8);
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_RELATIVE decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1706,7 +1713,7 @@
bData.privacy = inStream.read(2);
inStream.skip(6);
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "PRIVACY_INDICATOR decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1726,7 +1733,7 @@
decodeSuccess = true;
bData.language = inStream.read(8);
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "LANGUAGE_INDICATOR decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1747,7 +1754,7 @@
bData.displayMode = inStream.read(2);
inStream.skip(6);
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "DISPLAY_MODE decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1768,7 +1775,7 @@
bData.priority = inStream.read(2);
inStream.skip(6);
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "PRIORITY_INDICATOR decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1789,7 +1796,7 @@
bData.alert = inStream.read(2);
inStream.skip(6);
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "ALERT_ON_MESSAGE_DELIVERY decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1809,7 +1816,7 @@
decodeSuccess = true;
bData.userResponseCode = inStream.read(8);
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "USER_RESPONSE_CODE decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ")");
@@ -1871,7 +1878,7 @@
decodeSuccess = true;
}
- if ((! decodeSuccess) || (paramBits > 0)) {
+ if ((!decodeSuccess) || (paramBits > 0)) {
Rlog.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " +
(decodeSuccess ? "succeeded" : "failed") +
" (extra bits = " + paramBits + ')');
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index ccb1474..7e31c46 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -43,6 +43,7 @@
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
+import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -91,14 +92,15 @@
private int mVoiceMailCount = 0;
+ /** TP-Validity-Period-Format (TP-VPF). See TS 23.040, 9.2.3.3 */
private static final int VALIDITY_PERIOD_FORMAT_NONE = 0x00;
private static final int VALIDITY_PERIOD_FORMAT_ENHANCED = 0x01;
private static final int VALIDITY_PERIOD_FORMAT_RELATIVE = 0x02;
private static final int VALIDITY_PERIOD_FORMAT_ABSOLUTE = 0x03;
- //Validity Period min - 5 mins
+ // Validity Period min - 5 mins
private static final int VALIDITY_PERIOD_MIN = 5;
- //Validity Period max - 63 weeks
+ // Validity Period max - 63 weeks
private static final int VALIDITY_PERIOD_MAX = 635040;
private static final int INVALID_VALIDITY_PERIOD = -1;
@@ -139,38 +141,6 @@
}
/**
- * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
- * +CMT unsolicited response (PDU mode, of course)
- * +CMT: [<alpha>],<length><CR><LF><pdu>
- *
- * Only public for debugging
- *
- * {@hide}
- */
- public static SmsMessage newFromCMT(byte[] pdu) {
- try {
- SmsMessage msg = new SmsMessage();
- msg.parsePdu(pdu);
- return msg;
- } catch (RuntimeException ex) {
- Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
- return null;
- }
- }
-
- /** @hide */
- public static SmsMessage newFromCDS(byte[] pdu) {
- try {
- SmsMessage msg = new SmsMessage();
- msg.parsePdu(pdu);
- return msg;
- } catch (RuntimeException ex) {
- Rlog.e(LOG_TAG, "CDS SMS PDU parsing failed: ", ex);
- return null;
- }
- }
-
- /**
* Creates an SmsMessage from an SMS EF record.
*
* @param index Index of SMS EF record.
@@ -224,20 +194,20 @@
}
/**
- * Get Encoded Relative Validty Period Value from Validity period in mins.
+ * Gets Encoded Relative Validity Period Value from Validity period in mins.
*
* @param validityPeriod Validity period in mins.
*
* Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
- * ||relValidityPeriod (TP-VP) || || validityPeriod ||
- *
- * 0 to 143 ---> (TP-VP + 1) x 5 minutes
- *
- * 144 to 167 ---> 12 hours + ((TP-VP -143) x 30 minutes)
- *
- * 168 to 196 ---> (TP-VP - 166) x 1 day
- *
- * 197 to 255 ---> (TP-VP - 192) x 1 week
+ * ------------------------------------------------------------
+ * TP-VP | Validity period
+ * (Relative format) | value
+ * ------------------------------------------------------------
+ * 0 to 143 | (TP-VP + 1) x 5 minutes
+ * 144 to 167 | 12 hours + ((TP-VP -143) x 30 minutes)
+ * 168 to 196 | (TP-VP - 166) x 1 day
+ * 197 to 255 | (TP-VP - 192) x 1 week
+ * ------------------------------------------------------------
*
* @return relValidityPeriod Encoded Relative Validity Period Value.
* @hide
@@ -245,19 +215,16 @@
public static int getRelativeValidityPeriod(int validityPeriod) {
int relValidityPeriod = INVALID_VALIDITY_PERIOD;
- if (validityPeriod < VALIDITY_PERIOD_MIN || validityPeriod > VALIDITY_PERIOD_MAX) {
- Rlog.e(LOG_TAG,"Invalid Validity Period" + validityPeriod);
- return relValidityPeriod;
- }
-
- if (validityPeriod <= 720) {
- relValidityPeriod = (validityPeriod / 5) - 1;
- } else if (validityPeriod <= 1440) {
- relValidityPeriod = ((validityPeriod - 720) / 30) + 143;
- } else if (validityPeriod <= 43200) {
- relValidityPeriod = (validityPeriod / 1440) + 166;
- } else if (validityPeriod <= 635040) {
- relValidityPeriod = (validityPeriod / 10080) + 192;
+ if (validityPeriod >= VALIDITY_PERIOD_MIN) {
+ if (validityPeriod <= 720) {
+ relValidityPeriod = (validityPeriod / 5) - 1;
+ } else if (validityPeriod <= 1440) {
+ relValidityPeriod = ((validityPeriod - 720) / 30) + 143;
+ } else if (validityPeriod <= 43200) {
+ relValidityPeriod = (validityPeriod / 1440) + 166;
+ } else if (validityPeriod <= VALIDITY_PERIOD_MAX) {
+ relValidityPeriod = (validityPeriod / 10080) + 192;
+ }
}
return relValidityPeriod;
}
@@ -367,17 +334,19 @@
SubmitPdu ret = new SubmitPdu();
- int validityPeriodFormat = VALIDITY_PERIOD_FORMAT_NONE;
- int relativeValidityPeriod = INVALID_VALIDITY_PERIOD;
+ int relativeValidityPeriod = getRelativeValidityPeriod(validityPeriod);
- // TP-Validity-Period-Format (TP-VPF) in 3GPP TS 23.040 V6.8.1 section 9.2.3.3
- //bit 4:3 = 10 - TP-VP field present - relative format
- if((relativeValidityPeriod = getRelativeValidityPeriod(validityPeriod)) >= 0) {
- validityPeriodFormat = VALIDITY_PERIOD_FORMAT_RELATIVE;
+ byte mtiByte = 0x01; // SMS-SUBMIT
+
+ if (header != null) {
+ // Set TP-UDHI
+ mtiByte |= 0x40;
}
- byte mtiByte = (byte)(0x01 | (validityPeriodFormat << 0x03) |
- (header != null ? 0x40 : 0x00));
+ if (relativeValidityPeriod != INVALID_VALIDITY_PERIOD) {
+ // Set TP-Validity-Period-Format (TP-VPF)
+ mtiByte |= VALIDITY_PERIOD_FORMAT_RELATIVE << 3;
+ }
ByteArrayOutputStream bo = getSubmitPduHead(
scAddress, destinationAddress, mtiByte,
@@ -449,8 +418,8 @@
bo.write(0x08);
}
- if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_RELATIVE) {
- // ( TP-Validity-Period - relative format)
+ // TP-Validity-Period (TP-VP)
+ if (relativeValidityPeriod != INVALID_VALIDITY_PERIOD) {
bo.write(relativeValidityPeriod);
}
@@ -887,10 +856,9 @@
}
/**
- * Parses an SC timestamp and returns a currentTimeMillis()-style
- * timestamp
+ * Parses an SC timestamp and returns a currentTimeMillis()-style timestamp, or 0 if
+ * invalid.
*/
-
long getSCTimestampMillis() {
// TP-Service-Centre-Time-Stamp
int year = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
@@ -916,16 +884,22 @@
// It's 2006. Should I really support years < 2000?
int fullYear = year >= 90 ? year + 1900 : year + 2000;
- LocalDateTime localDateTime = LocalDateTime.of(
- fullYear,
- month /* 1-12 */,
- day,
- hour,
- minute,
- second);
- long epochSeconds = localDateTime.toEpochSecond(ZoneOffset.UTC) - timeZoneOffsetSeconds;
- // Convert to milliseconds.
- return epochSeconds * 1000;
+ try {
+ LocalDateTime localDateTime = LocalDateTime.of(
+ fullYear,
+ month /* 1-12 */,
+ day,
+ hour,
+ minute,
+ second);
+ long epochSeconds =
+ localDateTime.toEpochSecond(ZoneOffset.UTC) - timeZoneOffsetSeconds;
+ // Convert to milliseconds.
+ return epochSeconds * 1000;
+ } catch (DateTimeException ex) {
+ Rlog.e(LOG_TAG, "Invalid timestamp", ex);
+ }
+ return 0;
}
/**
@@ -1276,6 +1250,7 @@
mRecipientAddress = p.getAddress();
// TP-Service-Centre-Time-Stamp
mScTimeMillis = p.getSCTimestampMillis();
+ // TP-Discharge-Time
p.getSCTimestampMillis();
// TP-Status
mStatus = p.getByte();
@@ -1334,6 +1309,7 @@
+ " data coding scheme: " + mDataCodingScheme);
}
+ // TP-Service-Centre-Time-Stamp
mScTimeMillis = p.getSCTimestampMillis();
if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
@@ -1376,23 +1352,17 @@
// TP-Validity-Period-Format
int validityPeriodLength = 0;
- int validityPeriodFormat = ((firstByte>>3) & 0x3);
- if (0x0 == validityPeriodFormat) /* 00, TP-VP field not present*/
- {
+ int validityPeriodFormat = ((firstByte >> 3) & 0x3);
+ if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_NONE) {
validityPeriodLength = 0;
- }
- else if (0x2 == validityPeriodFormat) /* 10, TP-VP: relative format*/
- {
+ } else if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_RELATIVE) {
validityPeriodLength = 1;
- }
- else /* other case, 11 or 01, TP-VP: absolute or enhanced format*/
- {
+ } else { // VALIDITY_PERIOD_FORMAT_ENHANCED or VALIDITY_PERIOD_FORMAT_ABSOLUTE
validityPeriodLength = 7;
}
// TP-Validity-Period is not used on phone, so just ignore it for now.
- while (validityPeriodLength-- > 0)
- {
+ while (validityPeriodLength-- > 0) {
p.getByte();
}
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index baff952..070dacb 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -16,7 +16,7 @@
android_test {
name: "FlickerTests",
- srcs: ["src/**/*.java"],
+ srcs: ["src/**/*.java", "src/**/*.kt"],
manifest: "AndroidManifest.xml",
test_config: "AndroidTest.xml",
platform_apis: true,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java
deleted file mode 100644
index ed6f33e..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-import static android.view.Surface.rotationToString;
-
-import static com.android.server.wm.flicker.CommonTransitions.changeAppRotation;
-import static com.android.server.wm.flicker.WindowUtils.getAppPosition;
-import static com.android.server.wm.flicker.WindowUtils.getNavigationBarPosition;
-import static com.android.server.wm.flicker.WindowUtils.getStatusBarPosition;
-import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
-
-import android.graphics.Rect;
-import android.util.Log;
-import android.view.Surface;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-
-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;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-/**
- * Cycle through supported app rotations.
- * To run this test: {@code atest FlickerTest:ChangeAppRotationTest}
- */
-@LargeTest
-@RunWith(Parameterized.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class ChangeAppRotationTest extends FlickerTestBase {
- private int mBeginRotation;
- private int mEndRotation;
-
- public ChangeAppRotationTest(String beginRotationName, String endRotationName,
- int beginRotation, int endRotation) {
- this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "SimpleApp");
- this.mBeginRotation = beginRotation;
- this.mEndRotation = endRotation;
- }
-
- @Parameters(name = "{0}-{1}")
- public static Collection<Object[]> getParams() {
- int[] supportedRotations =
- {Surface.ROTATION_0, Surface.ROTATION_90};
- Collection<Object[]> params = new ArrayList<>();
- for (int begin : supportedRotations) {
- for (int end : supportedRotations) {
- if (begin != end) {
- params.add(new Object[]{rotationToString(begin), rotationToString(end), begin,
- end});
- }
- }
- }
- return params;
- }
-
- @Override
- TransitionRunner getTransitionToRun() {
- return changeAppRotation(mTestApp, mUiDevice, mBeginRotation, mEndRotation)
- .includeJankyRuns().build();
- }
-
- @FlakyTest(bugId = 140855415)
- @Ignore("Waiting bug feedback")
- @Test
- public void checkVisibility_navBarWindowIsAlwaysVisible() {
- checkResults(result -> assertThat(result)
- .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
- }
-
- @FlakyTest(bugId = 140855415)
- @Ignore("Waiting bug feedback")
- @Test
- public void checkVisibility_statusBarWindowIsAlwaysVisible() {
- checkResults(result -> assertThat(result)
- .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries());
- }
-
- @Test
- public void checkPosition_navBarLayerRotatesAndScales() {
- Rect startingPos = getNavigationBarPosition(mBeginRotation);
- Rect endingPos = getNavigationBarPosition(mEndRotation);
- checkResults(result -> {
- LayersTraceSubject.assertThat(result)
- .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
- .inTheBeginning();
- LayersTraceSubject.assertThat(result)
- .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, endingPos).atTheEnd();
- }
- );
- }
-
- @Test
- public void checkPosition_appLayerRotates() {
- Rect startingPos = getAppPosition(mBeginRotation);
- Rect endingPos = getAppPosition(mEndRotation);
- Log.e(TAG, "startingPos=" + startingPos + " endingPos=" + endingPos);
- checkResults(result -> {
- LayersTraceSubject.assertThat(result)
- .hasVisibleRegion(mTestApp.getPackage(), startingPos).inTheBeginning();
- LayersTraceSubject.assertThat(result)
- .hasVisibleRegion(mTestApp.getPackage(), endingPos).atTheEnd();
- }
- );
- }
-
- @Test
- public void checkPosition_statusBarLayerScales() {
- Rect startingPos = getStatusBarPosition(mBeginRotation);
- Rect endingPos = getStatusBarPosition(mEndRotation);
- checkResults(result -> {
- LayersTraceSubject.assertThat(result)
- .hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos)
- .inTheBeginning();
- LayersTraceSubject.assertThat(result)
- .hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, endingPos).atTheEnd();
- }
- );
- }
-
- @Ignore("Flaky. Pending debug")
- @Test
- public void checkVisibility_screenshotLayerBecomesInvisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(mTestApp.getPackage())
- .then()
- .replaceVisibleLayer(mTestApp.getPackage(), SCREENSHOT_LAYER)
- .then()
- .showsLayer(mTestApp.getPackage()).and().showsLayer(SCREENSHOT_LAYER)
- .then()
- .replaceVisibleLayer(SCREENSHOT_LAYER, mTestApp.getPackage())
- .forAllEntries());
- }
-
- @FlakyTest(bugId = 140855415)
- @Ignore("Waiting bug feedback")
- @Test
- public void checkVisibility_navBarLayerIsAlwaysVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
- }
-
- @FlakyTest(bugId = 140855415)
- @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/CloseImeAutoOpenWindowToAppTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToAppTest.java
deleted file mode 100644
index 58dcb99..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToAppTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.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());
- }
-
- @Override
- TransitionRunner getTransitionToRun() {
- return 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
deleted file mode 100644
index 7f610a6..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToHomeTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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.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());
- }
-
- @Override
- TransitionRunner getTransitionToRun() {
- return 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();
- }
-
- @FlakyTest(bugId = 157449248)
- @Ignore("Waiting bug feedback")
- @Test
- public void checkVisibility_imeAppWindowBecomesInvisible() {
- super.checkVisibility_imeAppWindowBecomesInvisible();
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java
deleted file mode 100644
index 57ad7e7..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToApp;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
-
-import com.android.server.wm.flicker.helpers.ImeAppHelper;
-
-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 CloseImeWindowToAppTest extends NonRotationTestBase {
-
- static final String IME_WINDOW_TITLE = "InputMethod";
-
- public CloseImeWindowToAppTest(String beginRotationName, int beginRotation) {
- super(beginRotationName, beginRotation);
-
- mTestApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
- }
-
- @Override
- TransitionRunner getTransitionToRun() {
- return editTextLoseFocusToApp((ImeAppHelper) mTestApp, mUiDevice, mBeginRotation)
- .includeJankyRuns().build();
- }
-
- @Ignore("Flaky. Pending debug")
- @Test
- public void checkVisibility_imeLayerBecomesInvisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(IME_WINDOW_TITLE)
- .then()
- .hidesLayer(IME_WINDOW_TITLE)
- .forAllEntries());
- }
-
- @Test
- public void checkVisibility_imeAppLayerIsAlwaysVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(mTestApp.getPackage())
- .forAllEntries());
- }
-
- @Test
- public void checkVisibility_imeAppWindowIsAlwaysVisible() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAppWindowOnTop(mTestApp.getPackage())
- .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
deleted file mode 100644
index 12d1179..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.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.ImeAppHelper;
-
-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 to home transitions.
- * To run this test: {@code atest FlickerTests:CloseImeWindowToHomeTest}
- */
-@LargeTest
-@RunWith(Parameterized.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class CloseImeWindowToHomeTest extends NonRotationTestBase {
-
- static final String IME_WINDOW_TITLE = "InputMethod";
-
- public CloseImeWindowToHomeTest(String beginRotationName, int beginRotation) {
- super(beginRotationName, beginRotation);
-
- mTestApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
- }
-
- @Override
- TransitionRunner getTransitionToRun() {
- return editTextLoseFocusToHome((ImeAppHelper) mTestApp, mUiDevice, mBeginRotation)
- .includeJankyRuns().build();
- }
-
- @Test
- public void checkVisibility_imeWindowBecomesInvisible() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .showsImeWindow(IME_WINDOW_TITLE)
- .then()
- .hidesImeWindow(IME_WINDOW_TITLE)
- .forAllEntries());
- }
-
- @FlakyTest(bugId = 153739621)
- @Ignore
- @Test
- public void checkVisibility_imeLayerBecomesInvisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .skipUntilFirstAssertion()
- .showsLayer(IME_WINDOW_TITLE)
- .then()
- .hidesLayer(IME_WINDOW_TITLE)
- .forAllEntries());
- }
-
- @FlakyTest(bugId = 153739621)
- @Ignore
- @Test
- public void checkVisibility_imeAppLayerBecomesInvisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .skipUntilFirstAssertion()
- .showsLayer(mTestApp.getPackage())
- .then()
- .hidesLayer(mTestApp.getPackage())
- .forAllEntries());
- }
-
- @Test
- public void checkVisibility_imeAppWindowBecomesInvisible() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAppWindowOnTop(mTestApp.getPackage())
- .then()
- .hidesAppWindowOnTop(mTestApp.getPackage())
- .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
deleted file mode 100644
index b1854c3..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-import static android.os.SystemClock.sleep;
-import static android.view.Surface.rotationToString;
-
-import static com.android.server.wm.flicker.helpers.AutomationUtils.clearRecents;
-import static com.android.server.wm.flicker.helpers.AutomationUtils.exitSplitScreen;
-import static com.android.server.wm.flicker.helpers.AutomationUtils.expandPipWindow;
-import static com.android.server.wm.flicker.helpers.AutomationUtils.launchSplitScreen;
-import static com.android.server.wm.flicker.helpers.AutomationUtils.stopPackage;
-
-import android.app.Instrumentation;
-import android.content.Context;
-import android.content.Intent;
-import android.os.RemoteException;
-import android.platform.helpers.IAppHelper;
-import android.util.Rational;
-import android.view.Surface;
-
-import androidx.annotation.Nullable;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-
-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.
- */
-class CommonTransitions {
-
- public static final int ITERATIONS = 1;
- private static final String TAG = "FLICKER";
- private static final long APP_LAUNCH_TIMEOUT = 10000;
-
- private static void setRotation(UiDevice device, int rotation) {
- try {
- switch (rotation) {
- case Surface.ROTATION_270:
- device.setOrientationLeft();
- break;
-
- case Surface.ROTATION_90:
- device.setOrientationRight();
- break;
-
- case Surface.ROTATION_0:
- default:
- device.setOrientationNatural();
- }
- // Wait for animation to complete
- sleep(1000);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Build a test tag for the test
- * @param testName Name of the transition(s) being tested
- * @param app App being launcher
- * @param rotation Initial screen rotation
- *
- * @return test tag with pattern <NAME>__<APP>__<ROTATION>
- */
- private static String buildTestTag(String testName, IAppHelper app, int rotation) {
- return buildTestTag(
- testName, app, /* app2 */ null, rotation, rotation, /* description */ "");
- }
-
- /**
- * Build a test tag for the test
- * @param testName Name of the transition(s) being tested
- * @param app App being launcher
- * @param beginRotation Initial screen rotation
- * @param endRotation End screen rotation (if any, otherwise use same as initial)
- *
- * @return test tag with pattern <NAME>__<APP>__<BEGIN_ROTATION>-<END_ROTATION>
- */
- private static String buildTestTag(String testName, IAppHelper app, int beginRotation,
- int endRotation) {
- return buildTestTag(
- testName, app, /* app2 */ null, beginRotation, endRotation, /* description */ "");
- }
-
- /**
- * Build a test tag for the test
- * @param testName Name of the transition(s) being tested
- * @param app App being launcher
- * @param app2 Second app being launched (if any)
- * @param beginRotation Initial screen rotation
- * @param endRotation End screen rotation (if any, otherwise use same as initial)
- * @param extraInfo Additional information to append to the tag
- *
- * @return test tag with pattern <NAME>__<APP(S)>__<ROTATION(S)>[__<EXTRA>]
- */
- private static String buildTestTag(String testName, IAppHelper app, @Nullable IAppHelper app2,
- int beginRotation, int endRotation, String extraInfo) {
- StringBuilder testTag = new StringBuilder();
- testTag.append(testName)
- .append("__")
- .append(app.getLauncherName());
-
- if (app2 != null) {
- testTag.append("-")
- .append(app2.getLauncherName());
- }
-
- testTag.append("__")
- .append(rotationToString(beginRotation));
-
- if (endRotation != beginRotation) {
- testTag.append("-")
- .append(rotationToString(endRotation));
- }
-
- if (!extraInfo.isEmpty()) {
- testTag.append("__")
- .append(extraInfo);
- }
-
- return testTag.toString();
- }
-
- static TransitionBuilder openAppWarm(IAppHelper testApp, UiDevice
- device, int beginRotation) {
- return TransitionRunner.newBuilder()
- .withTag(buildTestTag("openAppWarm", testApp, beginRotation))
- .recordAllRuns()
- .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)
- .repeat(ITERATIONS);
- }
-
- static TransitionBuilder closeAppWithBackKey(IAppHelper testApp, UiDevice
- device, int beginRotation) {
- return TransitionRunner.newBuilder()
- .withTag(buildTestTag("closeAppWithBackKey", testApp, beginRotation))
- .recordAllRuns()
- .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
- .runBefore(testApp::open)
- .runBefore(device::waitForIdle)
- .run(device::pressBack)
- .run(device::waitForIdle)
- .runAfterAll(testApp::exit)
- .runAfterAll(AutomationUtils::setDefaultWait)
- .repeat(ITERATIONS);
- }
-
- static TransitionBuilder closeAppWithHomeKey(IAppHelper testApp, UiDevice
- device, int beginRotation) {
- return TransitionRunner.newBuilder()
- .withTag(buildTestTag("closeAppWithHomeKey", testApp, beginRotation))
- .recordAllRuns()
- .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
- .runBefore(testApp::open)
- .runBefore(device::waitForIdle)
- .run(device::pressHome)
- .run(device::waitForIdle)
- .runAfterAll(testApp::exit)
- .runAfterAll(AutomationUtils::setDefaultWait)
- .repeat(ITERATIONS);
- }
-
- static TransitionBuilder openAppCold(IAppHelper testApp,
- UiDevice device, int beginRotation) {
- return TransitionRunner.newBuilder()
- .withTag(buildTestTag("openAppCold", testApp, beginRotation))
- .recordAllRuns()
- .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);
- }
-
- static TransitionBuilder changeAppRotation(IAppHelper testApp, UiDevice
- device, int beginRotation, int endRotation) {
- return TransitionRunner.newBuilder()
- .withTag(buildTestTag("changeAppRotation", testApp, beginRotation, endRotation))
- .recordAllRuns()
- .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
- .runBeforeAll(testApp::open)
- .runBefore(() -> setRotation(device, beginRotation))
- .run(() -> setRotation(device, endRotation))
- .runAfterAll(testApp::exit)
- .runAfterAll(() -> setRotation(device, Surface.ROTATION_0))
- .repeat(ITERATIONS);
- }
-
- static TransitionBuilder changeAppRotation(Intent intent, String intentId, Context context,
- UiDevice device, int beginRotation, int endRotation) {
- final String testTag = "changeAppRotation_" + intentId + "_" +
- rotationToString(beginRotation) + "_" + rotationToString(endRotation);
- return TransitionRunner.newBuilder()
- .withTag(testTag)
- .recordAllRuns()
- .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
- .runBeforeAll(() -> {
- context.startActivity(intent);
- device.wait(Until.hasObject(By.pkg(intent.getComponent()
- .getPackageName()).depth(0)), APP_LAUNCH_TIMEOUT);
- }
- )
- .runBefore(() -> setRotation(device, beginRotation))
- .run(() -> setRotation(device, endRotation))
- .runAfterAll(() -> stopPackage(context, intent.getComponent().getPackageName()))
- .runAfterAll(() -> setRotation(device, Surface.ROTATION_0))
- .repeat(ITERATIONS);
- }
-
- static TransitionBuilder appToSplitScreen(IAppHelper testApp, UiDevice device,
- int beginRotation) {
- return TransitionRunner.newBuilder()
- .withTag(buildTestTag("appToSplitScreen", testApp, beginRotation))
- .recordAllRuns()
- .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
- .runBeforeAll(() -> setRotation(device, beginRotation))
- .runBefore(testApp::open)
- .runBefore(device::waitForIdle)
- .runBefore(() -> sleep(500))
- .run(() -> launchSplitScreen(device))
- .runAfter(() -> exitSplitScreen(device))
- .runAfterAll(testApp::exit)
- .repeat(ITERATIONS);
- }
-
- static TransitionBuilder splitScreenToLauncher(IAppHelper testApp, UiDevice device,
- int beginRotation) {
- return TransitionRunner.newBuilder()
- .withTag(buildTestTag("splitScreenToLauncher", testApp, beginRotation))
- .recordAllRuns()
- .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
- .runBeforeAll(() -> setRotation(device, beginRotation))
- .runBefore(testApp::open)
- .runBefore(device::waitForIdle)
- .runBefore(() -> launchSplitScreen(device))
- .run(() -> exitSplitScreen(device))
- .runAfterAll(testApp::exit)
- .repeat(ITERATIONS);
- }
-
- static TransitionBuilder editTextSetFocus(ImeAppHelper testApp, UiDevice device,
- int beginRotation) {
- return TransitionRunner.newBuilder()
- .withTag(buildTestTag("editTextSetFocus", testApp, beginRotation))
- .recordAllRuns()
- .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
- .runBefore(device::pressHome)
- .runBefore(() -> setRotation(device, beginRotation))
- .runBefore(testApp::open)
- .run(() -> testApp.openIME(device))
- .runAfterAll(testApp::exit)
- .repeat(ITERATIONS);
- }
-
- static TransitionBuilder resizeSplitScreen(Instrumentation instr, IAppHelper testAppTop,
- ImeAppHelper testAppBottom, UiDevice device, int beginRotation, Rational startRatio,
- Rational stopRatio) {
- String description = startRatio.toString().replace("/", "-") + "_to_"
- + stopRatio.toString().replace("/", "-");
- String testTag = buildTestTag("resizeSplitScreen", testAppTop, testAppBottom,
- beginRotation, beginRotation, description);
- return TransitionRunner.newBuilder()
- .withTag(testTag)
- .recordAllRuns()
- .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
- .runBeforeAll(() -> setRotation(device, beginRotation))
- .runBeforeAll(() -> clearRecents(instr))
- .runBefore(testAppBottom::open)
- .runBefore(device::pressHome)
- .runBefore(testAppTop::open)
- .runBefore(device::waitForIdle)
- .runBefore(() -> launchSplitScreen(device))
- .runBefore(() -> {
- UiObject2 snapshot = device.findObject(
- By.res(device.getLauncherPackageName(), "snapshot"));
- snapshot.click();
- })
- .runBefore(() -> testAppBottom.openIME(device))
- .runBefore(device::pressBack)
- .runBefore(() -> AutomationUtils.resizeSplitScreen(device, startRatio))
- .run(() -> AutomationUtils.resizeSplitScreen(device, stopRatio))
- .runAfter(() -> exitSplitScreen(device))
- .runAfter(device::pressHome)
- .runAfterAll(testAppTop::exit)
- .runAfterAll(testAppBottom::exit)
- .repeat(ITERATIONS);
- }
-
- static TransitionBuilder editTextLoseFocusToHome(ImeAppHelper testApp, UiDevice device,
- int beginRotation) {
- return TransitionRunner.newBuilder()
- .withTag(buildTestTag("editTextLoseFocusToHome", testApp, beginRotation))
- .recordAllRuns()
- .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
- .runBefore(device::pressHome)
- .runBefore(() -> setRotation(device, beginRotation))
- .runBefore(testApp::open)
- .runBefore(() -> testApp.openIME(device))
- .run(device::pressHome)
- .run(device::waitForIdle)
- .runAfterAll(testApp::exit)
- .repeat(ITERATIONS);
- }
-
- static TransitionBuilder editTextLoseFocusToApp(ImeAppHelper testApp, UiDevice device,
- int beginRotation) {
- return TransitionRunner.newBuilder()
- .withTag(buildTestTag("editTextLoseFocusToApp", testApp, beginRotation))
- .recordAllRuns()
- .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
- .runBefore(device::pressHome)
- .runBefore(() -> setRotation(device, beginRotation))
- .runBefore(testApp::open)
- .runBefore(() -> testApp.openIME(device))
- .run(device::pressBack)
- .run(device::waitForIdle)
- .runAfterAll(testApp::exit)
- .repeat(ITERATIONS);
- }
-
- static TransitionBuilder enterPipMode(PipAppHelper testApp, UiDevice device,
- int beginRotation) {
- return TransitionRunner.newBuilder()
- .withTag(buildTestTag("enterPipMode", testApp, beginRotation))
- .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
- .runBefore(device::pressHome)
- .runBefore(() -> setRotation(device, beginRotation))
- .runBefore(testApp::open)
- .run(() -> testApp.clickEnterPipButton(device))
- .runAfter(() -> testApp.closePipWindow(device))
- .runAfterAll(testApp::exit)
- .repeat(ITERATIONS);
- }
-
- static TransitionBuilder exitPipModeToHome(PipAppHelper testApp, UiDevice device,
- int beginRotation) {
- return TransitionRunner.newBuilder()
- .withTag(buildTestTag("exitPipModeToHome", testApp, beginRotation))
- .recordAllRuns()
- .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
- .runBefore(device::pressHome)
- .runBefore(() -> setRotation(device, beginRotation))
- .runBefore(testApp::open)
- .run(() -> testApp.clickEnterPipButton(device))
- .run(() -> testApp.closePipWindow(device))
- .run(device::waitForIdle)
- .run(testApp::exit)
- .repeat(ITERATIONS);
- }
-
- static TransitionBuilder exitPipModeToApp(PipAppHelper testApp, UiDevice device,
- int beginRotation) {
- return TransitionRunner.newBuilder()
- .withTag(buildTestTag("exitPipModeToApp", testApp, beginRotation))
- .recordAllRuns()
- .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
- .run(device::pressHome)
- .run(() -> setRotation(device, beginRotation))
- .run(testApp::open)
- .run(() -> testApp.clickEnterPipButton(device))
- .run(() -> expandPipWindow(device))
- .run(device::waitForIdle)
- .run(testApp::exit)
- .repeat(ITERATIONS);
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.kt
new file mode 100644
index 0000000..b69e6a9
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.kt
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2020 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 android.app.Instrumentation
+import android.content.Context
+import android.content.Intent
+import android.os.RemoteException
+import android.os.SystemClock
+import android.platform.helpers.IAppHelper
+import android.util.Rational
+import android.view.Surface
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+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.
+ */
+internal object CommonTransitions {
+ private const val ITERATIONS = 1
+ private const val APP_LAUNCH_TIMEOUT: Long = 10000
+ private fun setRotation(device: UiDevice, rotation: Int) {
+ try {
+ when (rotation) {
+ Surface.ROTATION_270 -> device.setOrientationLeft()
+ Surface.ROTATION_90 -> device.setOrientationRight()
+ Surface.ROTATION_0 -> device.setOrientationNatural()
+ else -> device.setOrientationNatural()
+ }
+ // Wait for animation to complete
+ SystemClock.sleep(1000)
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ }
+ }
+
+ /**
+ * Build a test tag for the test
+ * @param testName Name of the transition(s) being tested
+ * @param app App being launcher
+ * @param rotation Initial screen rotation
+ *
+ * @return test tag with pattern <NAME>__<APP>__<ROTATION>
+ </ROTATION></APP></NAME> */
+ private fun buildTestTag(testName: String, app: IAppHelper, rotation: Int): String {
+ return buildTestTag(
+ testName, app, rotation, rotation, app2 = null, extraInfo = "")
+ }
+
+ /**
+ * Build a test tag for the test
+ * @param testName Name of the transition(s) being tested
+ * @param app App being launcher
+ * @param beginRotation Initial screen rotation
+ * @param endRotation End screen rotation (if any, otherwise use same as initial)
+ *
+ * @return test tag with pattern <NAME>__<APP>__<BEGIN_ROTATION>-<END_ROTATION>
+ </END_ROTATION></BEGIN_ROTATION></APP></NAME> */
+ private fun buildTestTag(
+ testName: String,
+ app: IAppHelper,
+ beginRotation: Int,
+ endRotation: Int
+ ): String {
+ return buildTestTag(
+ testName, app, beginRotation, endRotation, app2 = null, extraInfo = "")
+ }
+
+ /**
+ * Build a test tag for the test
+ * @param testName Name of the transition(s) being tested
+ * @param app App being launcher
+ * @param app2 Second app being launched (if any)
+ * @param beginRotation Initial screen rotation
+ * @param endRotation End screen rotation (if any, otherwise use same as initial)
+ * @param extraInfo Additional information to append to the tag
+ *
+ * @return test tag with pattern <NAME>__<APP></APP>(S)>__<ROTATION></ROTATION>(S)>[__<EXTRA>]
+ </EXTRA></NAME> */
+ private fun buildTestTag(
+ testName: String,
+ app: IAppHelper,
+ beginRotation: Int,
+ endRotation: Int,
+ app2: IAppHelper?,
+ extraInfo: String
+ ): String {
+ val testTag = StringBuilder()
+ testTag.append(testName)
+ .append("__")
+ .append(app.launcherName)
+ if (app2 != null) {
+ testTag.append("-")
+ .append(app2.launcherName)
+ }
+ testTag.append("__")
+ .append(Surface.rotationToString(beginRotation))
+ if (endRotation != beginRotation) {
+ testTag.append("-")
+ .append(Surface.rotationToString(endRotation))
+ }
+ if (extraInfo.isNotEmpty()) {
+ testTag.append("__")
+ .append(extraInfo)
+ }
+ return testTag.toString()
+ }
+
+ fun openAppWarm(
+ testApp: IAppHelper,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int
+ ): TransitionRunner.TransitionBuilder {
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(buildTestTag("openAppWarm", testApp, beginRotation))
+ .recordAllRuns()
+ .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() }
+ .repeat(ITERATIONS)
+ }
+
+ fun closeAppWithBackKey(
+ testApp: IAppHelper,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int
+ ): TransitionRunner.TransitionBuilder {
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(buildTestTag("closeAppWithBackKey", testApp, beginRotation))
+ .recordAllRuns()
+ .runBeforeAll { AutomationUtils.wakeUpAndGoToHomeScreen() }
+ .runBefore { testApp.open() }
+ .runBefore { device.waitForIdle() }
+ .run { device.pressBack() }
+ .run { device.waitForIdle() }
+ .runAfterAll { testApp.exit() }
+ .runAfterAll { AutomationUtils.setDefaultWait() }
+ .repeat(ITERATIONS)
+ }
+
+ fun closeAppWithHomeKey(
+ testApp: IAppHelper,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int
+ ): TransitionRunner.TransitionBuilder {
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(buildTestTag("closeAppWithHomeKey", testApp, beginRotation))
+ .recordAllRuns()
+ .runBeforeAll { AutomationUtils.wakeUpAndGoToHomeScreen() }
+ .runBefore { testApp.open() }
+ .runBefore { device.waitForIdle() }
+ .run { device.pressHome() }
+ .run { device.waitForIdle() }
+ .runAfterAll { testApp.exit() }
+ .runAfterAll { AutomationUtils.setDefaultWait() }
+ .repeat(ITERATIONS)
+ }
+
+ fun openAppCold(
+ testApp: IAppHelper,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int
+ ): TransitionRunner.TransitionBuilder {
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(buildTestTag("openAppCold", testApp, beginRotation))
+ .recordAllRuns()
+ .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)
+ }
+
+ fun changeAppRotation(
+ testApp: IAppHelper,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int,
+ endRotation: Int
+ ): TransitionRunner.TransitionBuilder {
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(buildTestTag("changeAppRotation", testApp, beginRotation, endRotation))
+ .recordAllRuns()
+ .runBeforeAll { AutomationUtils.wakeUpAndGoToHomeScreen() }
+ .runBeforeAll { testApp.open() }
+ .runBefore { setRotation(device, beginRotation) }
+ .run { setRotation(device, endRotation) }
+ .runAfterAll { testApp.exit() }
+ .runAfterAll { setRotation(device, Surface.ROTATION_0) }
+ .repeat(ITERATIONS)
+ }
+
+ fun changeAppRotation(
+ intent: Intent,
+ intentId: String,
+ context: Context,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int,
+ endRotation: Int
+ ): TransitionRunner.TransitionBuilder {
+ val testTag = "changeAppRotation_" + intentId + "_" +
+ Surface.rotationToString(beginRotation) + "_" +
+ Surface.rotationToString(endRotation)
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(testTag)
+ .recordAllRuns()
+ .runBeforeAll { AutomationUtils.wakeUpAndGoToHomeScreen() }
+ .runBeforeAll {
+ context.startActivity(intent)
+ device.wait(Until.hasObject(By.pkg(intent.component?.packageName)
+ .depth(0)), APP_LAUNCH_TIMEOUT)
+ }
+ .runBefore { setRotation(device, beginRotation) }
+ .run { setRotation(device, endRotation) }
+ .runAfterAll { AutomationUtils.stopPackage(context, intent.component?.packageName) }
+ .runAfterAll { setRotation(device, Surface.ROTATION_0) }
+ .repeat(ITERATIONS)
+ }
+
+ fun appToSplitScreen(
+ testApp: IAppHelper,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int
+ ): TransitionRunner.TransitionBuilder {
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(buildTestTag("appToSplitScreen", testApp, beginRotation))
+ .recordAllRuns()
+ .runBeforeAll { AutomationUtils.wakeUpAndGoToHomeScreen() }
+ .runBeforeAll { setRotation(device, beginRotation) }
+ .runBefore { testApp.open() }
+ .runBefore { device.waitForIdle() }
+ .runBefore { SystemClock.sleep(500) }
+ .run { AutomationUtils.launchSplitScreen(device) }
+ .runAfter { AutomationUtils.exitSplitScreen(device) }
+ .runAfterAll { testApp.exit() }
+ .repeat(ITERATIONS)
+ }
+
+ fun splitScreenToLauncher(
+ testApp: IAppHelper,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int
+ ): TransitionRunner.TransitionBuilder {
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(buildTestTag("splitScreenToLauncher", testApp, beginRotation))
+ .recordAllRuns()
+ .runBeforeAll { AutomationUtils.wakeUpAndGoToHomeScreen() }
+ .runBefore { testApp.open() }
+ .runBefore { device.waitForIdle() }
+ .runBefore { AutomationUtils.launchSplitScreen(device) }
+ .run { AutomationUtils.exitSplitScreen(device) }
+ .runAfterAll { testApp.exit() }
+ .repeat(ITERATIONS)
+ }
+
+ fun editTextSetFocus(
+ testApp: ImeAppHelper,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int
+ ): TransitionRunner.TransitionBuilder {
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(buildTestTag("editTextSetFocus", testApp, beginRotation))
+ .recordAllRuns()
+ .runBeforeAll { AutomationUtils.wakeUpAndGoToHomeScreen() }
+ .runBefore { device.pressHome() }
+ .runBefore { setRotation(device, beginRotation) }
+ .runBefore { testApp.open() }
+ .run { testApp.openIME(device) }
+ .runAfterAll { testApp.exit() }
+ .repeat(ITERATIONS)
+ }
+
+ fun resizeSplitScreen(
+ testAppTop: IAppHelper,
+ testAppBottom: ImeAppHelper,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int,
+ startRatio: Rational,
+ stopRatio: Rational
+ ): TransitionRunner.TransitionBuilder {
+ val description = (startRatio.toString().replace("/", "-") + "_to_" +
+ stopRatio.toString().replace("/", "-"))
+ val testTag = buildTestTag("resizeSplitScreen", testAppTop, beginRotation,
+ beginRotation, testAppBottom, description)
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(testTag)
+ .recordAllRuns()
+ .runBeforeAll { AutomationUtils.wakeUpAndGoToHomeScreen() }
+ .runBeforeAll { setRotation(device, beginRotation) }
+ .runBeforeAll { AutomationUtils.clearRecents(instrumentation) }
+ .runBefore { testAppBottom.open() }
+ .runBefore { device.pressHome() }
+ .runBefore { testAppTop.open() }
+ .runBefore { device.waitForIdle() }
+ .runBefore { AutomationUtils.launchSplitScreen(device) }
+ .runBefore {
+ val snapshot = device.findObject(
+ By.res(device.launcherPackageName, "snapshot"))
+ snapshot.click()
+ }
+ .runBefore { testAppBottom.openIME(device) }
+ .runBefore { device.pressBack() }
+ .runBefore { AutomationUtils.resizeSplitScreen(device, startRatio) }
+ .run { AutomationUtils.resizeSplitScreen(device, stopRatio) }
+ .runAfter { AutomationUtils.exitSplitScreen(device) }
+ .runAfter { device.pressHome() }
+ .runAfterAll { testAppTop.exit() }
+ .runAfterAll { testAppBottom.exit() }
+ .repeat(ITERATIONS)
+ }
+
+ fun editTextLoseFocusToHome(
+ testApp: ImeAppHelper,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int
+ ): TransitionRunner.TransitionBuilder {
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(buildTestTag("editTextLoseFocusToHome", testApp, beginRotation))
+ .recordAllRuns()
+ .runBeforeAll { AutomationUtils.wakeUpAndGoToHomeScreen() }
+ .runBefore { device.pressHome() }
+ .runBefore { setRotation(device, beginRotation) }
+ .runBefore { testApp.open() }
+ .runBefore { testApp.openIME(device) }
+ .run { device.pressHome() }
+ .run { device.waitForIdle() }
+ .runAfterAll { testApp.exit() }
+ .repeat(ITERATIONS)
+ }
+
+ fun editTextLoseFocusToApp(
+ testApp: ImeAppHelper,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int
+ ): TransitionRunner.TransitionBuilder {
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(buildTestTag("editTextLoseFocusToApp", testApp, beginRotation))
+ .recordAllRuns()
+ .runBeforeAll { AutomationUtils.wakeUpAndGoToHomeScreen() }
+ .runBefore { device.pressHome() }
+ .runBefore { setRotation(device, beginRotation) }
+ .runBefore { testApp.open() }
+ .runBefore { testApp.openIME(device) }
+ .run { device.pressBack() }
+ .run { device.waitForIdle() }
+ .runAfterAll { testApp.exit() }
+ .repeat(ITERATIONS)
+ }
+
+ fun enterPipMode(
+ testApp: PipAppHelper,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int
+ ): TransitionRunner.TransitionBuilder {
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(buildTestTag("enterPipMode", testApp, beginRotation))
+ .runBeforeAll { AutomationUtils.wakeUpAndGoToHomeScreen() }
+ .runBefore { device.pressHome() }
+ .runBefore { setRotation(device, beginRotation) }
+ .runBefore { testApp.open() }
+ .run { testApp.clickEnterPipButton(device) }
+ .runAfter { testApp.closePipWindow(device) }
+ .runAfterAll { testApp.exit() }
+ .repeat(ITERATIONS)
+ }
+
+ fun exitPipModeToHome(
+ testApp: PipAppHelper,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int
+ ): TransitionRunner.TransitionBuilder {
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(buildTestTag("exitPipModeToHome", testApp, beginRotation))
+ .recordAllRuns()
+ .runBeforeAll { AutomationUtils.wakeUpAndGoToHomeScreen() }
+ .runBefore { device.pressHome() }
+ .runBefore { setRotation(device, beginRotation) }
+ .runBefore { testApp.open() }
+ .run { testApp.clickEnterPipButton(device) }
+ .run { testApp.closePipWindow(device) }
+ .run { device.waitForIdle() }
+ .run { testApp.exit() }
+ .repeat(ITERATIONS)
+ }
+
+ fun exitPipModeToApp(
+ testApp: PipAppHelper,
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ beginRotation: Int
+ ): TransitionRunner.TransitionBuilder {
+ return TransitionRunner.TransitionBuilder(instrumentation)
+ .withTag(buildTestTag("exitPipModeToApp", testApp, beginRotation))
+ .recordAllRuns()
+ .runBeforeAll { AutomationUtils.wakeUpAndGoToHomeScreen() }
+ .run { device.pressHome() }
+ .run { setRotation(device, beginRotation) }
+ .run { testApp.open() }
+ .run { testApp.clickEnterPipButton(device) }
+ .run { AutomationUtils.expandPipWindow(device) }
+ .run { device.waitForIdle() }
+ .run { testApp.exit() }
+ .repeat(ITERATIONS)
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java
deleted file mode 100644
index dec5680..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-import android.app.Instrumentation;
-import android.platform.helpers.IAppHelper;
-import android.util.Rational;
-import android.view.Surface;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.UiDevice;
-
-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;
-import org.junit.runner.RunWith;
-import org.junit.runners.MethodSorters;
-
-/**
- * Tests to help debug individual transitions, capture video recordings and create test cases.
- */
-@LargeTest
-@Ignore("Used for debugging transitions used in FlickerTests.")
-@RunWith(AndroidJUnit4.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class DebugTest {
- private IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "SimpleApp");
- private UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-
- /**
- * atest FlickerTests:DebugTest#openAppCold
- */
- @Test
- public void openAppCold() {
- CommonTransitions.openAppCold(testApp, uiDevice, Surface.ROTATION_0)
- .recordAllRuns().build().run();
- }
-
- /**
- * atest FlickerTests:DebugTest#openAppWarm
- */
- @Test
- public void openAppWarm() {
- CommonTransitions.openAppWarm(testApp, uiDevice, Surface.ROTATION_0)
- .recordAllRuns().build().run();
- }
-
- /**
- * atest FlickerTests:DebugTest#changeOrientationFromNaturalToLeft
- */
- @Test
- public void changeOrientationFromNaturalToLeft() {
- CommonTransitions.changeAppRotation(testApp, uiDevice, Surface.ROTATION_0,
- Surface.ROTATION_270).recordAllRuns().build().run();
- }
-
- /**
- * atest FlickerTests:DebugTest#closeAppWithBackKey
- */
- @Test
- public void closeAppWithBackKey() {
- CommonTransitions.closeAppWithBackKey(testApp, uiDevice, Surface.ROTATION_0)
- .recordAllRuns().build().run();
- }
-
- /**
- * atest FlickerTests:DebugTest#closeAppWithHomeKey
- */
- @Test
- public void closeAppWithHomeKey() {
- CommonTransitions.closeAppWithHomeKey(testApp, uiDevice, Surface.ROTATION_0)
- .recordAllRuns().build().run();
- }
-
- /**
- * atest FlickerTests:DebugTest#openAppToSplitScreen
- */
- @Test
- public void openAppToSplitScreen() {
- CommonTransitions.appToSplitScreen(testApp, uiDevice,
- Surface.ROTATION_0).includeJankyRuns().recordAllRuns()
- .build().run();
- }
-
- /**
- * atest FlickerTests:DebugTest#splitScreenToLauncher
- */
- @Test
- public void splitScreenToLauncher() {
- CommonTransitions.splitScreenToLauncher(testApp, uiDevice, Surface.ROTATION_0)
- .includeJankyRuns().recordAllRuns().build().run();
- }
-
- /**
- * atest FlickerTests:DebugTest#resizeSplitScreen
- */
- @Test
- public void resizeSplitScreen() {
- Instrumentation instr = InstrumentationRegistry.getInstrumentation();
- ImeAppHelper bottomApp = new ImeAppHelper(instr);
- CommonTransitions.resizeSplitScreen(instr, testApp, bottomApp, uiDevice, Surface.ROTATION_0,
- new Rational(1, 3), new Rational(2, 3))
- .includeJankyRuns().build().run();
- }
-
- // IME tests
-
- /**
- * atest FlickerTests:DebugTest#editTextSetFocus
- */
- @Test
- public void editTextSetFocus() {
- ImeAppHelper testApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
- CommonTransitions.editTextSetFocus(testApp, uiDevice, Surface.ROTATION_0)
- .includeJankyRuns()
- .build().run();
- }
-
- /**
- * atest FlickerTests:DebugTest#editTextLoseFocusToHome
- */
- @Test
- public void editTextLoseFocusToHome() {
- ImeAppHelper testApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
- CommonTransitions.editTextLoseFocusToHome(testApp, uiDevice, Surface.ROTATION_0)
- .includeJankyRuns()
- .build().run();
- }
-
- /**
- * atest FlickerTests:DebugTest#editTextLoseFocusToApp
- */
- @Test
- public void editTextLoseFocusToApp() {
- ImeAppHelper testApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
- CommonTransitions.editTextLoseFocusToHome(testApp, uiDevice, Surface.ROTATION_0)
- .includeJankyRuns()
- .build().run();
- }
-
- // PIP tests
-
- /**
- * atest FlickerTests:DebugTest#enterPipMode
- */
- @Test
- public void enterPipMode() {
- PipAppHelper testApp = new PipAppHelper(InstrumentationRegistry.getInstrumentation());
- CommonTransitions.enterPipMode(testApp, uiDevice, Surface.ROTATION_0).includeJankyRuns()
- .build().run();
- }
-
- /**
- * atest FlickerTests:DebugTest#exitPipModeToHome
- */
- @Test
- public void exitPipModeToHome() {
- PipAppHelper testApp = new PipAppHelper(InstrumentationRegistry.getInstrumentation());
- CommonTransitions.exitPipModeToHome(testApp, uiDevice, Surface.ROTATION_0)
- .includeJankyRuns()
- .build().run();
- }
-
- /**
- * atest FlickerTests:DebugTest#exitPipModeToApp
- */
- @Test
- public void exitPipModeToApp() {
- PipAppHelper testApp = new PipAppHelper(InstrumentationRegistry.getInstrumentation());
- CommonTransitions.exitPipModeToApp(testApp, uiDevice, Surface.ROTATION_0).includeJankyRuns()
- .build().run();
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.kt
new file mode 100644
index 0000000..f871ea5
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 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 android.platform.helpers.IAppHelper
+import android.util.Rational
+import android.view.Surface
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.LargeTest
+import androidx.test.runner.AndroidJUnit4
+import androidx.test.uiautomator.UiDevice
+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
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+
+/**
+ * Tests to help debug individual transitions, capture video recordings and create test cases.
+ */
+@LargeTest
+@Ignore("Used for debugging transitions used in FlickerTests.")
+@RunWith(AndroidJUnit4::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class DebugTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val testApp: IAppHelper = StandardAppHelper(instrumentation,
+ "com.android.server.wm.flicker.testapp", "SimpleApp")
+ private val uiDevice = UiDevice.getInstance(instrumentation)
+
+ /**
+ * atest FlickerTests:DebugTest#openAppCold
+ */
+ @Test
+ fun openAppCold() {
+ CommonTransitions.openAppCold(testApp, instrumentation, uiDevice, Surface.ROTATION_0)
+ .recordAllRuns().build().run()
+ }
+
+ /**
+ * atest FlickerTests:DebugTest#openAppWarm
+ */
+ @Test
+ fun openAppWarm() {
+ CommonTransitions.openAppWarm(testApp, instrumentation, uiDevice, Surface.ROTATION_0)
+ .recordAllRuns().build().run()
+ }
+
+ /**
+ * atest FlickerTests:DebugTest#changeOrientationFromNaturalToLeft
+ */
+ @Test
+ fun changeOrientationFromNaturalToLeft() {
+ CommonTransitions.changeAppRotation(testApp, instrumentation, uiDevice, Surface.ROTATION_0,
+ Surface.ROTATION_270).recordAllRuns().build().run()
+ }
+
+ /**
+ * atest FlickerTests:DebugTest#closeAppWithBackKey
+ */
+ @Test
+ fun closeAppWithBackKey() {
+ CommonTransitions.closeAppWithBackKey(testApp, instrumentation, uiDevice,
+ Surface.ROTATION_0).recordAllRuns().build().run()
+ }
+
+ /**
+ * atest FlickerTests:DebugTest#closeAppWithHomeKey
+ */
+ @Test
+ fun closeAppWithHomeKey() {
+ CommonTransitions.closeAppWithHomeKey(testApp, instrumentation, uiDevice,
+ Surface.ROTATION_0).recordAllRuns().build().run()
+ }
+
+ /**
+ * atest FlickerTests:DebugTest#openAppToSplitScreen
+ */
+ @Test
+ fun openAppToSplitScreen() {
+ CommonTransitions.appToSplitScreen(testApp, instrumentation, uiDevice,
+ Surface.ROTATION_0).includeJankyRuns().recordAllRuns()
+ .build().run()
+ }
+
+ /**
+ * atest FlickerTests:DebugTest#splitScreenToLauncher
+ */
+ @Test
+ fun splitScreenToLauncher() {
+ CommonTransitions.splitScreenToLauncher(testApp, instrumentation, uiDevice,
+ Surface.ROTATION_0).includeJankyRuns().recordAllRuns().build().run()
+ }
+
+ /**
+ * atest FlickerTests:DebugTest#resizeSplitScreen
+ */
+ @Test
+ fun resizeSplitScreen() {
+ val bottomApp = ImeAppHelper(instrumentation)
+ CommonTransitions.resizeSplitScreen(
+ testApp,
+ bottomApp,
+ instrumentation,
+ uiDevice,
+ Surface.ROTATION_0,
+ Rational(1, 3), Rational(2, 3)
+ ).includeJankyRuns().build().run()
+ }
+ // IME tests
+ /**
+ * atest FlickerTests:DebugTest#editTextSetFocus
+ */
+ @Test
+ fun editTextSetFocus() {
+ val testApp = ImeAppHelper(instrumentation)
+ CommonTransitions.editTextSetFocus(testApp, instrumentation, uiDevice, Surface.ROTATION_0)
+ .includeJankyRuns()
+ .build().run()
+ }
+
+ /**
+ * atest FlickerTests:DebugTest#editTextLoseFocusToHome
+ */
+ @Test
+ fun editTextLoseFocusToHome() {
+ val testApp = ImeAppHelper(instrumentation)
+ CommonTransitions.editTextLoseFocusToHome(testApp, instrumentation, uiDevice,
+ Surface.ROTATION_0).includeJankyRuns().build().run()
+ }
+
+ /**
+ * atest FlickerTests:DebugTest#editTextLoseFocusToApp
+ */
+ @Test
+ fun editTextLoseFocusToApp() {
+ val testApp = ImeAppHelper(instrumentation)
+ CommonTransitions.editTextLoseFocusToHome(testApp, instrumentation, uiDevice,
+ Surface.ROTATION_0).includeJankyRuns().build().run()
+ }
+ // PIP tests
+ /**
+ * atest FlickerTests:DebugTest#enterPipMode
+ */
+ @Test
+ fun enterPipMode() {
+ val testApp = PipAppHelper(instrumentation)
+ CommonTransitions.enterPipMode(testApp, instrumentation, uiDevice, Surface.ROTATION_0)
+ .includeJankyRuns().build().run()
+ }
+
+ /**
+ * atest FlickerTests:DebugTest#exitPipModeToHome
+ */
+ @Test
+ fun exitPipModeToHome() {
+ val testApp = PipAppHelper(instrumentation)
+ CommonTransitions.exitPipModeToHome(testApp, instrumentation, uiDevice, Surface.ROTATION_0)
+ .includeJankyRuns()
+ .build().run()
+ }
+
+ /**
+ * atest FlickerTests:DebugTest#exitPipModeToApp
+ */
+ @Test
+ fun exitPipModeToApp() {
+ val testApp = PipAppHelper(instrumentation)
+ CommonTransitions.exitPipModeToApp(testApp, instrumentation, uiDevice, Surface.ROTATION_0)
+ .includeJankyRuns().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
deleted file mode 100644
index 6bbf684..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-import static com.android.server.wm.flicker.helpers.AutomationUtils.setDefaultWait;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.platform.helpers.IAppHelper;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.uiautomator.UiDevice;
-
-import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
-
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * Base class of all Flicker test that performs common functions for all flicker tests:
- * <p>
- * - Caches transitions so that a transition is run once and the transition results are used by
- * tests multiple times. This is needed for parameterized tests which call the BeforeClass methods
- * multiple times.
- * - Keeps track of all test artifacts and deletes ones which do not need to be reviewed.
- * - Fails tests if results are not available for any test due to jank.
- */
-public abstract class FlickerTestBase {
- public static final String TAG = "FLICKER";
- static final String SCREENSHOT_LAYER = "RotationLayer";
- static final String NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar";
- static final String STATUS_BAR_WINDOW_TITLE = "StatusBar";
- static final String DOCKED_STACK_DIVIDER = "DockedStackDivider";
- private static HashMap<String, List<TransitionResult>> transitionResults =
- new HashMap<>();
- IAppHelper mTestApp;
- UiDevice mUiDevice;
- private List<TransitionResult> mResults;
- private TransitionResult mLastResult = null;
-
- @Before
- public void setUp() {
- mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- }
-
- /**
- * Teardown any system settings and clean up test artifacts from the file system.
- *
- * Note: test artifacts for failed tests will remain on the device.
- */
- @AfterClass
- public static void teardown() {
- setDefaultWait();
- transitionResults.values().stream()
- .flatMap(List::stream)
- .forEach(result -> {
- if (result.canDelete()) {
- result.delete();
- } else {
- if (result.layersTraceExists()) {
- Log.e(TAG, "Layers trace saved to " + result.getLayersTracePath());
- }
- if (result.windowManagerTraceExists()) {
- Log.e(TAG, "WindowManager trace saved to " + result
- .getWindowManagerTracePath
- ());
- }
- if (result.screenCaptureVideoExists()) {
- Log.e(TAG, "Screen capture video saved to " + result
- .screenCaptureVideoPath().toString());
- }
- }
- });
- }
-
- /**
- * Runs a transition, returns a cached result if the transition has run before.
- */
- void run(TransitionRunner transition) {
- if (transitionResults.containsKey(transition.getTestTag())) {
- mResults = transitionResults.get(transition.getTestTag());
- return;
- }
- mResults = transition.run().getResults();
- /* Fail if we don't have any results due to jank */
- assertWithMessage("No results to test because all transition runs were invalid because "
- + "of Jank").that(mResults).isNotEmpty();
- transitionResults.put(transition.getTestTag(), mResults);
- }
-
- /**
- * Runs a transition, returns a cached result if the transition has run before.
- */
- @Before
- public void runTransition() {
- run(getTransitionToRun());
- }
-
- /**
- * Gets the transition that will be executed
- */
- abstract TransitionRunner getTransitionToRun();
-
- /**
- * Goes through a list of transition results and checks assertions on each result.
- */
- void checkResults(Consumer<TransitionResult> assertion) {
-
- for (TransitionResult result : mResults) {
- mLastResult = result;
- assertion.accept(result);
- }
- mLastResult = null;
- }
-
- /**
- * Kludge to mark a file for saving. If {@code checkResults} fails, the last result is not
- * cleared. This indicates the assertion failed for the result, so mark it for saving.
- */
- @After
- public void markArtifactsForSaving() {
- if (mLastResult != null) {
- mLastResult.flagForSaving();
- }
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.kt
new file mode 100644
index 0000000..d7586d0
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2020 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 android.platform.helpers.IAppHelper
+import android.util.Log
+import androidx.test.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.server.wm.flicker.helpers.AutomationUtils
+import com.google.common.truth.Truth
+import org.junit.After
+import org.junit.AfterClass
+import org.junit.Before
+
+/**
+ * Base class of all Flicker test that performs common functions for all flicker tests:
+ *
+ *
+ * - Caches transitions so that a transition is run once and the transition results are used by
+ * tests multiple times. This is needed for parameterized tests which call the BeforeClass methods
+ * multiple times.
+ * - Keeps track of all test artifacts and deletes ones which do not need to be reviewed.
+ * - Fails tests if results are not available for any test due to jank.
+ */
+abstract class FlickerTestBase {
+ lateinit var testApp: IAppHelper
+ open val instrumentation by lazy {
+ InstrumentationRegistry.getInstrumentation()
+ }
+ val uiDevice by lazy {
+ UiDevice.getInstance(instrumentation)
+ }
+ lateinit var tesults: List<TransitionResult>
+ private var lastResult: TransitionResult? = null
+
+ /**
+ * Runs a transition, returns a cached result if the transition has run before.
+ */
+ fun run(transition: TransitionRunner) {
+ if (transitionResults.containsKey(transition.testTag)) {
+ tesults = transitionResults[transition.testTag]
+ ?: throw IllegalStateException("Results do not contain test tag " +
+ transition.testTag)
+ return
+ }
+ tesults = transition.run().results
+ /* Fail if we don't have any results due to jank */
+ Truth.assertWithMessage("No results to test because all transition runs were invalid " +
+ "because of Jank").that(tesults).isNotEmpty()
+ transitionResults[transition.testTag] = tesults
+ }
+
+ /**
+ * Runs a transition, returns a cached result if the transition has run before.
+ */
+ @Before
+ fun runTransition() {
+ run(transitionToRun)
+ }
+
+ /**
+ * Gets the transition that will be executed
+ */
+ abstract val transitionToRun: TransitionRunner
+
+ /**
+ * Goes through a list of transition results and checks assertions on each result.
+ */
+ fun checkResults(assertion: (TransitionResult) -> Unit) {
+ for (result in tesults) {
+ lastResult = result
+ assertion(result)
+ }
+ lastResult = null
+ }
+
+ /**
+ * Kludge to mark a file for saving. If `checkResults` fails, the last result is not
+ * cleared. This indicates the assertion failed for the result, so mark it for saving.
+ */
+ @After
+ fun markArtifactsForSaving() {
+ lastResult?.flagForSaving()
+ }
+
+ companion object {
+ const val TAG = "FLICKER"
+ const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
+ const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
+ const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
+ private val transitionResults = mutableMapOf<String, List<TransitionResult>>()
+
+ /**
+ * Teardown any system settings and clean up test artifacts from the file system.
+ *
+ * Note: test artifacts for failed tests will remain on the device.
+ */
+ @AfterClass
+ @JvmStatic
+ fun teardown() {
+ AutomationUtils.setDefaultWait()
+ transitionResults.values
+ .flatten()
+ .forEach {
+ if (it.canDelete()) {
+ it.delete()
+ } else {
+ if (it.layersTraceExists()) {
+ Log.e(TAG, "Layers trace saved to ${it.layersTracePath}")
+ }
+ if (it.windowManagerTraceExists()) {
+ Log.e(TAG,
+ "WindowManager trace saved to ${it.windowManagerTracePath}")
+ }
+ if (it.screenCaptureVideoExists()) {
+ Log.e(TAG,
+ "Screen capture video saved to ${it.screenCaptureVideoPath()}")
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.java b/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.java
deleted file mode 100644
index 54941dc..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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/NonRotationTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.kt
new file mode 100644
index 0000000..653fecd
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 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 android.view.Surface
+import androidx.test.filters.FlakyTest
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runners.Parameterized
+
+abstract class NonRotationTestBase(
+ beginRotationName: String,
+ protected val beginRotation: Int
+) : FlickerTestBase() {
+ @FlakyTest(bugId = 141361128)
+ @Ignore("Waiting bug feedback")
+ @Test
+ fun checkCoveredRegion_noUncoveredRegions() {
+ val displayBounds = WindowUtils.getDisplayBounds(beginRotation)
+ checkResults {
+ LayersTraceSubject.assertThat(it).coversRegion(
+ displayBounds).forAllEntries()
+ }
+ }
+
+ @FlakyTest(bugId = 141361128)
+ @Ignore("Waiting bug feedback")
+ @Test
+ fun checkVisibility_navBarLayerIsAlwaysVisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()
+ }
+ }
+
+ @FlakyTest(bugId = 141361128)
+ @Ignore("Waiting bug feedback")
+ @Test
+ fun checkVisibility_statusBarLayerIsAlwaysVisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries()
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
+ val params: MutableCollection<Array<Any>> = ArrayList()
+ for (begin in supportedRotations) {
+ params.add(arrayOf(Surface.rotationToString(begin), begin))
+ }
+ return params
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java
deleted file mode 100644
index c150e09..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-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 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 cold launch app from launcher.
- * To run this test: {@code atest FlickerTests:OpenAppColdTest}
- */
-@LargeTest
-@RunWith(Parameterized.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class OpenAppColdTest extends NonRotationTestBase {
-
- public OpenAppColdTest(String beginRotationName, int beginRotation) {
- super(beginRotationName, beginRotation);
-
- this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "SimpleApp");
- }
-
- @Override
- TransitionRunner getTransitionToRun() {
- return openAppCold(mTestApp, mUiDevice, mBeginRotation)
- .includeJankyRuns().build();
- }
-
- @Test
- public void checkVisibility_wallpaperWindowBecomesInvisible() {
- checkResults(result -> assertThat(result)
- .showsBelowAppWindow("Wallpaper")
- .then()
- .hidesBelowAppWindow("Wallpaper")
- .forAllEntries());
- }
-
- @FlakyTest(bugId = 140855415)
- @Ignore("Waiting bug feedback")
- @Test
- public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
- checkResults(result -> assertThat(result)
- .showsAppWindowOnTop(
- "com.android.launcher3/.Launcher")
- .then()
- .showsAppWindowOnTop(mTestApp.getPackage())
- .forAllEntries());
- }
-
- @Test
- public void checkVisibility_wallpaperLayerBecomesInvisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer("Wallpaper")
- .then()
- .replaceVisibleLayer("Wallpaper", mTestApp.getPackage())
- .forAllEntries());
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppToSplitScreenTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppToSplitScreenTest.java
deleted file mode 100644
index 00da62f..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppToSplitScreenTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-import static com.android.server.wm.flicker.CommonTransitions.appToSplitScreen;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
-
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.MethodSorters;
-import org.junit.runners.Parameterized;
-
-/**
- * Test open app to split screen.
- * To run this test: {@code atest FlickerTests:OpenAppToSplitScreenTest}
- */
-@LargeTest
-@RunWith(Parameterized.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class OpenAppToSplitScreenTest extends NonRotationTestBase {
-
- public OpenAppToSplitScreenTest(String beginRotationName, int beginRotation) {
- super(beginRotationName, beginRotation);
-
- this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "SimpleApp");
- }
-
- @Override
- TransitionRunner getTransitionToRun() {
- return appToSplitScreen(mTestApp, mUiDevice, mBeginRotation)
- .includeJankyRuns()
- .build();
- }
-
- @Test
- public void checkVisibility_navBarWindowIsAlwaysVisible() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
- }
-
- @Test
- public void checkVisibility_statusBarWindowIsAlwaysVisible() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries());
- }
-
- @Test
- public void checkVisibility_dividerLayerBecomesVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .hidesLayer(DOCKED_STACK_DIVIDER)
- .then()
- .showsLayer(DOCKED_STACK_DIVIDER)
- .forAllEntries());
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java
deleted file mode 100644
index 62ae254..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-import static com.android.server.wm.flicker.CommonTransitions.openAppWarm;
-import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-
-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 warm launch app.
- * To run this test: {@code atest FlickerTests:OpenAppWarmTest}
- */
-@LargeTest
-@RunWith(Parameterized.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class OpenAppWarmTest extends NonRotationTestBase {
-
- public OpenAppWarmTest(String beginRotationName, int beginRotation) {
- super(beginRotationName, beginRotation);
-
- this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "SimpleApp");
- }
-
- @Override
- TransitionRunner getTransitionToRun() {
- return openAppWarm(mTestApp, mUiDevice, mBeginRotation)
- .includeJankyRuns().build();
- }
-
- @Test
- public void checkVisibility_wallpaperBecomesInvisible() {
- checkResults(result -> assertThat(result)
- .showsBelowAppWindow("Wallpaper")
- .then()
- .hidesBelowAppWindow("Wallpaper")
- .forAllEntries());
- }
-
- @FlakyTest(bugId = 140855415)
- @Ignore("Waiting bug feedback")
- @Test
- public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
- checkResults(result -> assertThat(result)
- .showsAppWindowOnTop(
- "com.android.launcher3/.Launcher")
- .then()
- .showsAppWindowOnTop(mTestApp.getPackage())
- .forAllEntries());
- }
-
- @Test
- public void checkVisibility_wallpaperLayerBecomesInvisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer("Wallpaper")
- .then()
- .replaceVisibleLayer("Wallpaper", mTestApp.getPackage())
- .forAllEntries());
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java
deleted file mode 100644
index fcb022c..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-import static com.android.server.wm.flicker.CommonTransitions.editTextSetFocus;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
-
-import com.android.server.wm.flicker.helpers.ImeAppHelper;
-
-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(Parameterized.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-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());
- }
-
- @Override
- TransitionRunner getTransitionToRun() {
- return editTextSetFocus((ImeAppHelper) mTestApp, mUiDevice, mBeginRotation)
- .includeJankyRuns().build();
- }
-
- @Test
- public void checkVisibility_imeWindowBecomesVisible() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .skipUntilFirstAssertion()
- .hidesImeWindow(IME_WINDOW_TITLE)
- .then()
- .showsImeWindow(IME_WINDOW_TITLE)
- .forAllEntries());
- }
-
- @Test
- public void checkVisibility_imeLayerBecomesVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .hidesLayer(IME_WINDOW_TITLE)
- .then()
- .showsLayer(IME_WINDOW_TITLE)
- .forAllEntries());
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/PipTestBase.java b/tests/FlickerTests/src/com/android/server/wm/flicker/PipTestBase.java
deleted file mode 100644
index cf51d78..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/PipTestBase.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2020 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.helpers.AutomationUtils.closePipWindow;
-import static com.android.server.wm.flicker.helpers.AutomationUtils.hasPipWindow;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
-import androidx.test.uiautomator.UiDevice;
-
-import com.android.server.wm.flicker.helpers.PipAppHelper;
-
-import org.junit.AfterClass;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.MethodSorters;
-import org.junit.runners.Parameterized;
-
-@LargeTest
-@RunWith(Parameterized.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public abstract class PipTestBase extends NonRotationTestBase {
- static final String sPipWindowTitle = "PipMenuActivity";
-
- public PipTestBase(String beginRotationName, int beginRotation) {
- super(beginRotationName, beginRotation);
-
- this.mTestApp = new PipAppHelper(InstrumentationRegistry.getInstrumentation());
- }
-
- @AfterClass
- public static void teardown() {
- UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-
- if (hasPipWindow(device)) {
- closePipWindow(device);
- }
- }
-
- @Test
- public void checkVisibility_pipWindowBecomesVisible() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .skipUntilFirstAssertion()
- .showsAppWindowOnTop(sPipWindowTitle)
- .then()
- .hidesAppWindow(sPipWindowTitle)
- .forAllEntries());
- }
-
- @Test
- public void checkVisibility_pipLayerBecomesVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .skipUntilFirstAssertion()
- .showsLayer(sPipWindowTitle)
- .then()
- .hidesLayer(sPipWindowTitle)
- .forAllEntries());
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/PipToAppTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/PipToAppTest.java
deleted file mode 100644
index 5e67ada..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/PipToAppTest.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.exitPipModeToApp;
-
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-
-import com.android.server.wm.flicker.helpers.PipAppHelper;
-
-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 Pip launch.
- * To run this test: {@code atest FlickerTests:PipToAppTest}
- */
-@LargeTest
-@RunWith(Parameterized.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 152738416)
-@Ignore("Waiting bug feedback")
-public class PipToAppTest extends PipTestBase {
- public PipToAppTest(String beginRotationName, int beginRotation) {
- super(beginRotationName, beginRotation);
- }
-
- @Override
- TransitionRunner getTransitionToRun() {
- return exitPipModeToApp((PipAppHelper) mTestApp, mUiDevice, mBeginRotation)
- .includeJankyRuns().build();
- }
-
- @Test
- public void checkVisibility_backgroundWindowVisibleBehindPipLayer() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .skipUntilFirstAssertion()
- .showsAppWindowOnTop(sPipWindowTitle)
- .then()
- .showsBelowAppWindow("Wallpaper")
- .then()
- .showsAppWindowOnTop(mTestApp.getPackage())
- .then()
- .hidesAppWindowOnTop(mTestApp.getPackage())
- .forAllEntries());
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/PipToHomeTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/PipToHomeTest.java
deleted file mode 100644
index af713c7..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/PipToHomeTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.exitPipModeToHome;
-
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-
-import com.android.server.wm.flicker.helpers.PipAppHelper;
-
-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 Pip launch.
- * To run this test: {@code atest FlickerTests:PipToHomeTest}
- */
-@LargeTest
-@RunWith(Parameterized.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 152738416)
-@Ignore("Waiting bug feedback")
-public class PipToHomeTest extends PipTestBase {
- public PipToHomeTest(String beginRotationName, int beginRotation) {
- super(beginRotationName, beginRotation);
- }
-
- @Override
- TransitionRunner getTransitionToRun() {
- return exitPipModeToHome((PipAppHelper) mTestApp, mUiDevice, mBeginRotation)
- .includeJankyRuns().build();
- }
-
- @Ignore
- @Test
- public void checkVisibility_backgroundWindowVisibleBehindPipLayer() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAppWindowOnTop(sPipWindowTitle)
- .then()
- .showsBelowAppWindow("Wallpaper")
- .then()
- .showsAppWindowOnTop("Wallpaper")
- .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
deleted file mode 100644
index 9516af6..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-import static com.android.server.wm.flicker.CommonTransitions.resizeSplitScreen;
-import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
-import static com.android.server.wm.flicker.WindowUtils.getDockedStackDividerInset;
-import static com.android.server.wm.flicker.WindowUtils.getNavigationBarHeight;
-import static com.android.server.wm.flicker.helpers.AutomationUtils.exitSplitScreen;
-import static com.android.server.wm.flicker.helpers.AutomationUtils.isInSplitScreen;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.Instrumentation;
-import android.graphics.Rect;
-import android.util.Rational;
-import android.view.Surface;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.UiDevice;
-
-import com.android.server.wm.flicker.helpers.ImeAppHelper;
-
-import org.junit.AfterClass;
-import org.junit.FixMethodOrder;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.MethodSorters;
-
-/**
- * Test split screen resizing window transitions.
- * To run this test: {@code atest FlickerTests:ResizeSplitScreenTest}
- *
- * Currently it runs only in 0 degrees because of b/156100803
- */
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 159096424)
-@Ignore("Waiting bug feedback")
-public class ResizeSplitScreenTest extends FlickerTestBase {
-
- private static String sSimpleActivity = "SimpleActivity";
- private static String sImeActivity = "ImeActivity";
-
- public ResizeSplitScreenTest() {
- this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "SimpleApp");
- }
-
- @Override
- TransitionRunner getTransitionToRun() {
- Instrumentation instr = InstrumentationRegistry.getInstrumentation();
- ImeAppHelper bottomApp = new ImeAppHelper(instr);
- return resizeSplitScreen(instr, mTestApp, bottomApp, mUiDevice, Surface.ROTATION_0,
- new Rational(1, 3), new Rational(2, 3))
- .includeJankyRuns().build();
- }
-
- @Test
- public void checkVisibility_topAppLayerIsAlwaysVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(sSimpleActivity)
- .forAllEntries());
- }
-
- @Test
- public void checkVisibility_bottomAppLayerIsAlwaysVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(sImeActivity)
- .forAllEntries());
- }
-
- @Test
- public void checkVisibility_dividerLayerIsAlwaysVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(DOCKED_STACK_DIVIDER)
- .forAllEntries());
- }
-
- @Test
- @Ignore("Waiting feedback")
- public void checkPosition_appsStartingBounds() {
- Rect displayBounds = getDisplayBounds();
- checkResults(result -> {
- LayersTrace entries = LayersTrace.parseFrom(result.getLayersTrace(),
- result.getLayersTracePath(), result.getLayersTraceChecksum());
-
- assertThat(entries.getEntries()).isNotEmpty();
- Rect startingDividerBounds = entries.getEntries().get(0).getVisibleBounds
- (DOCKED_STACK_DIVIDER);
-
- Rect startingTopAppBounds = new Rect(0, 0, startingDividerBounds.right,
- startingDividerBounds.top + getDockedStackDividerInset());
-
- Rect startingBottomAppBounds = new Rect(0,
- startingDividerBounds.bottom - getDockedStackDividerInset(),
- displayBounds.right,
- displayBounds.bottom - getNavigationBarHeight());
-
- LayersTraceSubject.assertThat(result)
- .hasVisibleRegion("SimpleActivity", startingTopAppBounds)
- .inTheBeginning();
-
- LayersTraceSubject.assertThat(result)
- .hasVisibleRegion("ImeActivity", startingBottomAppBounds)
- .inTheBeginning();
- });
- }
-
- @Test
- @Ignore("Waiting feedback")
- public void checkPosition_appsEndingBounds() {
- Rect displayBounds = getDisplayBounds();
- checkResults(result -> {
- LayersTrace entries = LayersTrace.parseFrom(result.getLayersTrace(),
- result.getLayersTracePath(), result.getLayersTraceChecksum());
-
- assertThat(entries.getEntries()).isNotEmpty();
- Rect endingDividerBounds = entries.getEntries().get(
- entries.getEntries().size() - 1).getVisibleBounds(
- DOCKED_STACK_DIVIDER);
-
- Rect startingTopAppBounds = new Rect(0, 0, endingDividerBounds.right,
- endingDividerBounds.top + getDockedStackDividerInset());
-
- Rect startingBottomAppBounds = new Rect(0,
- endingDividerBounds.bottom - getDockedStackDividerInset(),
- displayBounds.right,
- displayBounds.bottom - getNavigationBarHeight());
-
- LayersTraceSubject.assertThat(result)
- .hasVisibleRegion(sSimpleActivity, startingTopAppBounds)
- .atTheEnd();
-
- LayersTraceSubject.assertThat(result)
- .hasVisibleRegion(sImeActivity, startingBottomAppBounds)
- .atTheEnd();
- });
- }
-
- @Test
- public void checkVisibility_navBarWindowIsAlwaysVisible() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE)
- .forAllEntries());
- }
-
- @Test
- public void checkVisibility_statusBarWindowIsAlwaysVisible() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE)
- .forAllEntries());
- }
-
- @Test
- @FlakyTest(bugId = 156223549)
- @Ignore("Waiting bug feedback")
- public void checkVisibility_topAppWindowIsAlwaysVisible() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAppWindow(sSimpleActivity)
- .forAllEntries());
- }
-
- @Test
- @FlakyTest(bugId = 156223549)
- @Ignore("Waiting bug feedback")
- public void checkVisibility_bottomAppWindowIsAlwaysVisible() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAppWindow(sImeActivity)
- .forAllEntries());
- }
-
- @AfterClass
- public static void teardown() {
- UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-
- if (isInSplitScreen(device)) {
- exitSplitScreen(device);
- }
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/RotationTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/RotationTestBase.kt
new file mode 100644
index 0000000..873d607
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/RotationTestBase.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2020 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 android.view.Surface
+import androidx.test.filters.FlakyTest
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runners.Parameterized
+
+abstract class RotationTestBase(
+ beginRotationName: String,
+ endRotationName: String,
+ protected val beginRotation: Int,
+ protected val endRotation: Int
+) : FlickerTestBase() {
+ @FlakyTest(bugId = 140855415)
+ @Ignore("Waiting bug feedback")
+ @Test
+ fun checkVisibility_navBarWindowIsAlwaysVisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()
+ }
+ }
+
+ @FlakyTest(bugId = 140855415)
+ @Ignore("Waiting bug feedback")
+ @Test
+ fun checkVisibility_statusBarWindowIsAlwaysVisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries()
+ }
+ }
+
+ @Test
+ fun checkPosition_navBarLayerRotatesAndScales() {
+ val startingPos = WindowUtils.getNavigationBarPosition(beginRotation)
+ val endingPos = WindowUtils.getNavigationBarPosition(endRotation)
+ if (startingPos == endingPos) {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+ .forAllEntries()
+ }
+ } else {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+ .inTheBeginning()
+ }
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, endingPos)
+ .atTheEnd()
+ }
+ }
+ }
+
+ @Test
+ fun checkPosition_statusBarLayerRotatesScales() {
+ val startingPos = WindowUtils.getStatusBarPosition(beginRotation)
+ val endingPos = WindowUtils.getStatusBarPosition(endRotation)
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos)
+ .inTheBeginning()
+ LayersTraceSubject.assertThat(it)
+ .hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, endingPos).atTheEnd()
+ }
+ }
+
+ @FlakyTest(bugId = 140855415)
+ @Ignore("Waiting bug feedback")
+ @Test
+ fun checkVisibility_navBarLayerIsAlwaysVisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()
+ }
+ }
+
+ @FlakyTest(bugId = 140855415)
+ @Ignore("Waiting bug feedback")
+ @Test
+ fun checkVisibility_statusBarLayerIsAlwaysVisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries()
+ }
+ }
+
+ companion object {
+ const val SCREENSHOT_LAYER = "RotationLayer"
+
+ @Parameterized.Parameters(name = "{0}-{1}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
+ val params: MutableCollection<Array<Any>> = mutableListOf()
+ for (begin in supportedRotations) {
+ for (end in supportedRotations) {
+ if (begin != end) {
+ params.add(arrayOf(
+ Surface.rotationToString(begin),
+ Surface.rotationToString(end),
+ begin,
+ end
+ ))
+ }
+ }
+ }
+ return params
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java
deleted file mode 100644
index 3cff868..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-import static android.view.Surface.rotationToString;
-
-import static com.android.server.wm.flicker.CommonTransitions.changeAppRotation;
-import static com.android.server.wm.flicker.WindowUtils.getAppPosition;
-import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
-import static com.android.server.wm.flicker.WindowUtils.getNavigationBarPosition;
-import static com.android.server.wm.flicker.testapp.ActivityOptions.EXTRA_STARVE_UI_THREAD;
-import static com.android.server.wm.flicker.testapp.ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME;
-
-import android.content.Intent;
-import android.graphics.Rect;
-import android.view.Surface;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-
-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;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-/**
- * Cycle through supported app rotations using seamless rotations.
- * To run this test: {@code atest FlickerTests:SeamlessAppRotationTest}
- */
-@LargeTest
-@RunWith(Parameterized.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 147659548)
-@Ignore("Waiting bug feedback")
-public class SeamlessAppRotationTest extends FlickerTestBase {
- private int mBeginRotation;
- private int mEndRotation;
- private Intent mIntent;
-
- public SeamlessAppRotationTest(String testId, Intent intent, int beginRotation,
- int endRotation) {
- this.mIntent = intent;
- this.mBeginRotation = beginRotation;
- this.mEndRotation = endRotation;
- }
-
- @Parameters(name = "{0}")
- public static Collection<Object[]> getParams() {
- int[] supportedRotations =
- {Surface.ROTATION_0, Surface.ROTATION_90};
- Collection<Object[]> params = new ArrayList<>();
-
- ArrayList<Intent> testIntents = new ArrayList<>();
-
- // launch test activity that supports seamless rotation
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setComponent(SEAMLESS_ACTIVITY_COMPONENT_NAME);
- testIntents.add(intent);
-
- // launch test activity that supports seamless rotation with a busy UI thread to miss frames
- // when the app is asked to redraw
- intent = new Intent(intent);
- intent.putExtra(EXTRA_STARVE_UI_THREAD, true);
- testIntents.add(intent);
-
- for (Intent testIntent : testIntents) {
- for (int begin : supportedRotations) {
- for (int end : supportedRotations) {
- if (begin != end) {
- String testId = rotationToString(begin) + "_" + rotationToString(end);
- if (testIntent.getExtras() != null &&
- testIntent.getExtras().getBoolean(EXTRA_STARVE_UI_THREAD)) {
- testId += "_" + "BUSY_UI_THREAD";
- }
- params.add(new Object[]{testId, testIntent, begin, end});
- }
- }
- }
- }
- return params;
- }
-
- @Override
- TransitionRunner getTransitionToRun() {
- String intentId = "";
- if (mIntent.getExtras() != null &&
- mIntent.getExtras().getBoolean(EXTRA_STARVE_UI_THREAD)) {
- intentId = "BUSY_UI_THREAD";
- }
-
- return changeAppRotation(mIntent, intentId, InstrumentationRegistry.getContext(),
- mUiDevice, mBeginRotation, mEndRotation).build();
- }
-
- @Test
- public void checkVisibility_navBarWindowIsAlwaysVisible() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
- }
-
- @Test
- public void checkPosition_navBarLayerRotatesAndScales() {
- Rect startingPos = getNavigationBarPosition(mBeginRotation);
- Rect endingPos = getNavigationBarPosition(mEndRotation);
- if (startingPos.equals(endingPos)) {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
- .forAllEntries());
- } else {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
- .inTheBeginning());
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, endingPos)
- .atTheEnd());
- }
- }
-
- @Test
- public void checkPosition_appLayerRotates() {
- Rect startingPos = getAppPosition(mBeginRotation);
- Rect endingPos = getAppPosition(mEndRotation);
- if (startingPos.equals(endingPos)) {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .hasVisibleRegion(mIntent.getComponent().getPackageName(), startingPos)
- .forAllEntries());
- } else {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .hasVisibleRegion(mIntent.getComponent().getPackageName(), startingPos)
- .then()
- .hasVisibleRegion(mIntent.getComponent().getPackageName(), endingPos)
- .forAllEntries());
- }
- }
-
- @Test
- public void checkCoveredRegion_noUncoveredRegions() {
- Rect startingBounds = getDisplayBounds(mBeginRotation);
- Rect endingBounds = getDisplayBounds(mEndRotation);
- if (startingBounds.equals(endingBounds)) {
- checkResults(result ->
- LayersTraceSubject.assertThat(result)
- .coversRegion(startingBounds)
- .forAllEntries());
- } else {
- checkResults(result ->
- LayersTraceSubject.assertThat(result)
- .coversRegion(startingBounds)
- .then()
- .coversRegion(endingBounds)
- .forAllEntries());
- }
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/SplitScreenToLauncherTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/SplitScreenToLauncherTest.java
deleted file mode 100644
index 9e9c829..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/SplitScreenToLauncherTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-import static com.android.server.wm.flicker.CommonTransitions.splitScreenToLauncher;
-import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
-import static com.android.server.wm.flicker.helpers.AutomationUtils.exitSplitScreen;
-import static com.android.server.wm.flicker.helpers.AutomationUtils.isInSplitScreen;
-
-import android.view.Surface;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.UiDevice;
-
-import org.junit.AfterClass;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.MethodSorters;
-
-/**
- * Test open app to split screen.
- * To run this test: {@code atest FlickerTests:SplitScreenToLauncherTest}
- */
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class SplitScreenToLauncherTest extends FlickerTestBase {
-
- public SplitScreenToLauncherTest() {
- this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
- "com.android.server.wm.flicker.testapp", "SimpleApp");
- }
-
- @Override
- TransitionRunner getTransitionToRun() {
- return splitScreenToLauncher(mTestApp, mUiDevice, Surface.ROTATION_0)
- .includeJankyRuns().build();
- }
-
- @Test
- public void checkCoveredRegion_noUncoveredRegions() {
- checkResults(result ->
- LayersTraceSubject.assertThat(result)
- .coversRegion(getDisplayBounds()).forAllEntries());
- }
-
- @Test
- public void checkVisibility_dividerLayerBecomesInVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(DOCKED_STACK_DIVIDER)
- .then()
- .hidesLayer(DOCKED_STACK_DIVIDER)
- .forAllEntries());
- }
-
- @Test
- public void checkVisibility_appLayerBecomesInVisible() {
- checkResults(result -> LayersTraceSubject.assertThat(result)
- .showsLayer(mTestApp.getPackage())
- .then()
- .hidesLayer(mTestApp.getPackage())
- .forAllEntries());
- }
-
- @Test
- public void checkVisibility_navBarWindowIsAlwaysVisible() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
- }
-
- @Test
- public void checkVisibility_statusBarWindowIsAlwaysVisible() {
- checkResults(result -> WmTraceSubject.assertThat(result)
- .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries());
- }
-
- @AfterClass
- public static void teardown() {
- UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-
- if (isInSplitScreen(device)) {
- exitSplitScreen(device);
- }
- }
-}
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
deleted file mode 100644
index 42977f5..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerAppHelper.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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/FlickerAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerAppHelper.kt
new file mode 100644
index 0000000..e579533
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerAppHelper.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 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
+
+abstract class FlickerAppHelper(
+ instr: Instrumentation,
+ launcherName: String
+) : StandardAppHelper(instr, sFlickerPackage, launcherName) {
+ companion object {
+ var sFindTimeout = 10000
+ var sFlickerPackage = "com.android.server.wm.flicker.testapp"
+ }
+}
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
deleted file mode 100644
index 1b2c484..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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 androidx.test.uiautomator.UiDevice;
-
-public class ImeAppAutoFocusHelper extends ImeAppHelper {
-
- public ImeAppAutoFocusHelper(Instrumentation instr) {
- super(instr, "ImeAppAutoFocus");
- }
-
- public void openIME(UiDevice device) {
- // do nothing (the app is focused automatically)
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/Cancellable.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
similarity index 64%
rename from services/core/java/com/android/server/biometrics/sensors/Cancellable.java
rename to tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index 68a3bc6..d587f1e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/Cancellable.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-package com.android.server.biometrics.sensors;
+package com.android.server.wm.flicker.helpers
-/**
- * Interface that cancellable {@link ClientMonitor} subclasses should implement.
- */
-public interface Cancellable {
- /**
- * Requests to end the ClientMonitor's lifecycle.
- */
- void cancel();
+import android.app.Instrumentation
+import androidx.test.uiautomator.UiDevice
+
+class ImeAppAutoFocusHelper(instr: Instrumentation) : ImeAppHelper(instr, "ImeAppAutoFocus") {
+ override fun openIME(device: UiDevice) {
+ // 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
deleted file mode 100644
index 095722d..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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 org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-import android.app.Instrumentation;
-
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-
-public class ImeAppHelper extends FlickerAppHelper {
-
- ImeAppHelper(Instrumentation instr, String launcherName) {
- super(instr, launcherName);
- }
-
- public ImeAppHelper(Instrumentation instr) {
- this(instr, "ImeApp");
- }
-
- public void openIME(UiDevice device) {
- UiObject2 editText = device.wait(
- Until.findObject(By.res(getPackage(), "plain_text_input")),
- AutomationUtils.FIND_TIMEOUT);
- assertNotNull("Text field not found, this usually happens when the device was left "
- + "in an unknown state (e.g. in split screen)", editText);
- editText.click();
-
- if (!AutomationUtils.waitForIME(device)) {
- fail("IME did not appear");
- }
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
new file mode 100644
index 0000000..979cbea
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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 androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import org.junit.Assert
+
+open class ImeAppHelper(
+ instr: Instrumentation,
+ launcherName: String = "ImeApp"
+) : FlickerAppHelper(instr, launcherName) {
+ open fun openIME(device: UiDevice) {
+ val editText = device.wait(
+ Until.findObject(By.res(getPackage(), "plain_text_input")),
+ AutomationUtils.FIND_TIMEOUT)
+ Assert.assertNotNull("Text field not found, this usually happens when the device " +
+ "was left in an unknown state (e.g. in split screen)", editText)
+ editText.click()
+ if (!AutomationUtils.waitForIME(device)) {
+ Assert.fail("IME did not appear")
+ }
+ }
+}
\ No newline at end of file
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
deleted file mode 100644
index f2b7a7e3..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.hasPipWindow;
-
-import static org.junit.Assert.assertNotNull;
-
-import android.app.Instrumentation;
-
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
-
-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"));
- assertNotNull("Pip button not found, this usually happens when the device was left "
- + "in an unknown state (e.g. in split screen)", enterPipButton);
- enterPipButton.click();
- hasPipWindow(device);
- }
-
- public void closePipWindow(UiDevice device) {
- AutomationUtils.closePipWindow(device);
- }
-
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
new file mode 100644
index 0000000..daee810
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 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 androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import org.junit.Assert
+
+class PipAppHelper(instr: Instrumentation) : FlickerAppHelper(instr, "PipApp") {
+ fun clickEnterPipButton(device: UiDevice) {
+ val enterPipButton = device.findObject(By.res(getPackage(), "enter_pip"))
+ Assert.assertNotNull("Pip button not found, this usually happens when the device " +
+ "was left in an unknown state (e.g. in split screen)", enterPipButton)
+ enterPipButton.click()
+ AutomationUtils.hasPipWindow(device)
+ }
+
+ fun closePipWindow(device: UiDevice) {
+ AutomationUtils.closePipWindow(device)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
new file mode 100644
index 0000000..6946618
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 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.ime
+
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.LargeTest
+import com.android.server.wm.flicker.CommonTransitions
+import com.android.server.wm.flicker.TransitionRunner
+import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+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: `atest FlickerTests:CloseImeWindowToAppTest`
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class CloseImeAutoOpenWindowToAppTest(
+ beginRotationName: String,
+ beginRotation: Int
+) : CloseImeWindowToAppTest(beginRotationName, beginRotation) {
+ init {
+ testApp = ImeAppAutoFocusHelper(instrumentation)
+ }
+
+ override val transitionToRun: TransitionRunner
+ get() = CommonTransitions.editTextLoseFocusToApp(testApp as ImeAppAutoFocusHelper,
+ instrumentation, uiDevice, beginRotation)
+ .includeJankyRuns().build()
+
+ @FlakyTest(bugId = 141458352)
+ @Ignore("Waiting bug feedback")
+ @Test
+ override fun checkVisibility_imeLayerBecomesInvisible() {
+ super.checkVisibility_imeLayerBecomesInvisible()
+ }
+
+ @FlakyTest(bugId = 141458352)
+ @Ignore("Waiting bug feedback")
+ @Test
+ override fun checkVisibility_imeAppLayerIsAlwaysVisible() {
+ super.checkVisibility_imeAppLayerIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 141458352)
+ @Ignore("Waiting bug feedback")
+ @Test
+ override fun checkVisibility_imeAppWindowIsAlwaysVisible() {
+ super.checkVisibility_imeAppWindowIsAlwaysVisible()
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
new file mode 100644
index 0000000..a1030d8
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 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.ime
+
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.LargeTest
+import com.android.server.wm.flicker.CommonTransitions
+import com.android.server.wm.flicker.TransitionRunner
+import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+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: `atest FlickerTests:CloseImeWindowToAppTest`
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class CloseImeAutoOpenWindowToHomeTest(
+ beginRotationName: String,
+ beginRotation: Int
+) : CloseImeWindowToHomeTest(beginRotationName, beginRotation) {
+ init {
+ testApp = ImeAppAutoFocusHelper(instrumentation)
+ }
+
+ override val transitionToRun: TransitionRunner
+ get() = CommonTransitions.editTextLoseFocusToHome(testApp as ImeAppAutoFocusHelper,
+ instrumentation, uiDevice, beginRotation)
+ .includeJankyRuns().build()
+
+ @FlakyTest(bugId = 141458352)
+ @Ignore("Waiting bug feedback")
+ @Test
+ override fun checkVisibility_imeWindowBecomesInvisible() {
+ super.checkVisibility_imeWindowBecomesInvisible()
+ }
+
+ @FlakyTest(bugId = 141458352)
+ @Ignore("Waiting bug feedback")
+ @Test
+ override fun checkVisibility_imeLayerBecomesInvisible() {
+ super.checkVisibility_imeLayerBecomesInvisible()
+ }
+
+ @FlakyTest(bugId = 157449248)
+ @Ignore("Waiting bug feedback")
+ @Test
+ override fun checkVisibility_imeAppWindowBecomesInvisible() {
+ super.checkVisibility_imeAppWindowBecomesInvisible()
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
new file mode 100644
index 0000000..6e7c92b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 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.ime
+
+import androidx.test.filters.LargeTest
+import com.android.server.wm.flicker.CommonTransitions
+import com.android.server.wm.flicker.LayersTraceSubject
+import com.android.server.wm.flicker.NonRotationTestBase
+import com.android.server.wm.flicker.TransitionRunner
+import com.android.server.wm.flicker.WmTraceSubject
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+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: `atest FlickerTests:CloseImeWindowToAppTest`
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class CloseImeWindowToAppTest(
+ beginRotationName: String,
+ beginRotation: Int
+) : NonRotationTestBase(beginRotationName, beginRotation) {
+ init {
+ testApp = ImeAppHelper(instrumentation)
+ }
+
+ override val transitionToRun: TransitionRunner
+ get() = CommonTransitions.editTextLoseFocusToApp(testApp as ImeAppHelper,
+ instrumentation, uiDevice, beginRotation)
+ .includeJankyRuns().build()
+
+ @Ignore("Flaky. Pending debug")
+ @Test
+ open fun checkVisibility_imeLayerBecomesInvisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .showsLayer(IME_WINDOW_TITLE)
+ .then()
+ .hidesLayer(IME_WINDOW_TITLE)
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ open fun checkVisibility_imeAppLayerIsAlwaysVisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .showsLayer(testApp.getPackage())
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ open fun checkVisibility_imeAppWindowIsAlwaysVisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAppWindowOnTop(testApp.getPackage())
+ .forAllEntries()
+ }
+ }
+
+ companion object {
+ const val IME_WINDOW_TITLE = "InputMethod"
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
new file mode 100644
index 0000000..7b155eb
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 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.ime
+
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.LargeTest
+import com.android.server.wm.flicker.CommonTransitions
+import com.android.server.wm.flicker.LayersTraceSubject
+import com.android.server.wm.flicker.NonRotationTestBase
+import com.android.server.wm.flicker.TransitionRunner
+import com.android.server.wm.flicker.WmTraceSubject
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+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 to home transitions.
+ * To run this test: `atest FlickerTests:CloseImeWindowToHomeTest`
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class CloseImeWindowToHomeTest(
+ beginRotationName: String,
+ beginRotation: Int
+) : NonRotationTestBase(beginRotationName, beginRotation) {
+ init {
+ testApp = ImeAppHelper(instrumentation)
+ }
+
+ override val transitionToRun: TransitionRunner
+ get() = CommonTransitions.editTextLoseFocusToHome(testApp as ImeAppHelper,
+ instrumentation, uiDevice, beginRotation)
+ .includeJankyRuns().build()
+
+ @Test
+ open fun checkVisibility_imeWindowBecomesInvisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsImeWindow(IME_WINDOW_TITLE)
+ .then()
+ .hidesImeWindow(IME_WINDOW_TITLE)
+ .forAllEntries()
+ }
+ }
+
+ @FlakyTest(bugId = 153739621)
+ @Ignore
+ @Test
+ open fun checkVisibility_imeLayerBecomesInvisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .skipUntilFirstAssertion()
+ .showsLayer(IME_WINDOW_TITLE)
+ .then()
+ .hidesLayer(IME_WINDOW_TITLE)
+ .forAllEntries()
+ }
+ }
+
+ @FlakyTest(bugId = 153739621)
+ @Ignore
+ @Test
+ fun checkVisibility_imeAppLayerBecomesInvisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .skipUntilFirstAssertion()
+ .showsLayer(testApp.getPackage())
+ .then()
+ .hidesLayer(testApp.getPackage())
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ open fun checkVisibility_imeAppWindowBecomesInvisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAppWindowOnTop(testApp.getPackage())
+ .then()
+ .hidesAppWindowOnTop(testApp.getPackage())
+ .forAllEntries()
+ }
+ }
+
+ companion object {
+ const val IME_WINDOW_TITLE: String = "InputMethod"
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
new file mode 100644
index 0000000..9885359
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 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.ime
+
+import androidx.test.filters.LargeTest
+import com.android.server.wm.flicker.CommonTransitions
+import com.android.server.wm.flicker.LayersTraceSubject
+import com.android.server.wm.flicker.NonRotationTestBase
+import com.android.server.wm.flicker.TransitionRunner
+import com.android.server.wm.flicker.WmTraceSubject
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+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: `atest FlickerTests:OpenImeWindowTest`
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenImeWindowTest(
+ beginRotationName: String,
+ beginRotation: Int
+) : NonRotationTestBase(beginRotationName, beginRotation) {
+ init {
+ testApp = ImeAppHelper(instrumentation)
+ }
+
+ override val transitionToRun: TransitionRunner
+ get() = CommonTransitions.editTextSetFocus(testApp as ImeAppHelper,
+ instrumentation, uiDevice, beginRotation)
+ .includeJankyRuns().build()
+
+ @Test
+ fun checkVisibility_imeWindowBecomesVisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .skipUntilFirstAssertion()
+ .hidesImeWindow(IME_WINDOW_TITLE)
+ .then()
+ .showsImeWindow(IME_WINDOW_TITLE)
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ fun checkVisibility_imeLayerBecomesVisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .hidesLayer(IME_WINDOW_TITLE)
+ .then()
+ .showsLayer(IME_WINDOW_TITLE)
+ .forAllEntries()
+ }
+ }
+
+ companion object {
+ private const val IME_WINDOW_TITLE = "InputMethod"
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
new file mode 100644
index 0000000..943a525
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 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.launch
+
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.LargeTest
+import com.android.server.wm.flicker.CommonTransitions
+import com.android.server.wm.flicker.LayersTraceSubject
+import com.android.server.wm.flicker.NonRotationTestBase
+import com.android.server.wm.flicker.StandardAppHelper
+import com.android.server.wm.flicker.TransitionRunner
+import com.android.server.wm.flicker.WmTraceSubject
+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 cold launch app from launcher.
+ * To run this test: `atest FlickerTests:OpenAppColdTest`
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppColdTest(
+ beginRotationName: String,
+ beginRotation: Int
+) : NonRotationTestBase(beginRotationName, beginRotation) {
+ init {
+ testApp = StandardAppHelper(instrumentation,
+ "com.android.server.wm.flicker.testapp", "SimpleApp")
+ }
+
+ override val transitionToRun: TransitionRunner
+ get() = CommonTransitions.openAppCold(testApp, instrumentation, uiDevice, beginRotation)
+ .includeJankyRuns().build()
+
+ @Test
+ fun checkVisibility_wallpaperWindowBecomesInvisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsBelowAppWindow("Wallpaper")
+ .then()
+ .hidesBelowAppWindow("Wallpaper")
+ .forAllEntries()
+ }
+ }
+
+ @FlakyTest(bugId = 140855415)
+ @Ignore("Waiting bug feedback")
+ @Test
+ fun checkZOrder_appWindowReplacesLauncherAsTopWindow() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAppWindowOnTop(
+ "com.android.launcher3/.Launcher")
+ .then()
+ .showsAppWindowOnTop(testApp.getPackage())
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ fun checkVisibility_wallpaperLayerBecomesInvisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .showsLayer("Wallpaper")
+ .then()
+ .replaceVisibleLayer("Wallpaper", testApp.getPackage())
+ .forAllEntries()
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
new file mode 100644
index 0000000..7964d94
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 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.launch
+
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.LargeTest
+import com.android.server.wm.flicker.CommonTransitions
+import com.android.server.wm.flicker.LayersTraceSubject
+import com.android.server.wm.flicker.NonRotationTestBase
+import com.android.server.wm.flicker.StandardAppHelper
+import com.android.server.wm.flicker.TransitionRunner
+import com.android.server.wm.flicker.WmTraceSubject
+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 warm launch app.
+ * To run this test: `atest FlickerTests:OpenAppWarmTest`
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppWarmTest(
+ beginRotationName: String,
+ beginRotation: Int
+) : NonRotationTestBase(beginRotationName, beginRotation) {
+ init {
+ testApp = StandardAppHelper(instrumentation,
+ "com.android.server.wm.flicker.testapp", "SimpleApp")
+ }
+
+ override val transitionToRun: TransitionRunner
+ get() = CommonTransitions.openAppWarm(testApp, instrumentation, uiDevice, beginRotation)
+ .includeJankyRuns().build()
+
+ @Test
+ fun checkVisibility_wallpaperBecomesInvisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsBelowAppWindow("Wallpaper")
+ .then()
+ .hidesBelowAppWindow("Wallpaper")
+ .forAllEntries()
+ }
+ }
+
+ @FlakyTest(bugId = 140855415)
+ @Ignore("Waiting bug feedback")
+ @Test
+ fun checkZOrder_appWindowReplacesLauncherAsTopWindow() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAppWindowOnTop(
+ "com.android.launcher3/.Launcher")
+ .then()
+ .showsAppWindowOnTop(testApp.getPackage())
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ fun checkVisibility_wallpaperLayerBecomesInvisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .showsLayer("Wallpaper")
+ .then()
+ .replaceVisibleLayer("Wallpaper", testApp.getPackage())
+ .forAllEntries()
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipTestBase.kt
new file mode 100644
index 0000000..79321f9
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipTestBase.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 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.pip
+
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.LargeTest
+import androidx.test.uiautomator.UiDevice
+import com.android.server.wm.flicker.LayersTraceSubject
+import com.android.server.wm.flicker.NonRotationTestBase
+import com.android.server.wm.flicker.WmTraceSubject
+import com.android.server.wm.flicker.helpers.AutomationUtils
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import org.junit.AfterClass
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+abstract class PipTestBase(
+ beginRotationName: String,
+ beginRotation: Int
+) : NonRotationTestBase(beginRotationName, beginRotation) {
+ init {
+ testApp = PipAppHelper(instrumentation)
+ }
+
+ @Test
+ fun checkVisibility_pipWindowBecomesVisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .skipUntilFirstAssertion()
+ .showsAppWindowOnTop(sPipWindowTitle)
+ .then()
+ .hidesAppWindow(sPipWindowTitle)
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ fun checkVisibility_pipLayerBecomesVisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .skipUntilFirstAssertion()
+ .showsLayer(sPipWindowTitle)
+ .then()
+ .hidesLayer(sPipWindowTitle)
+ .forAllEntries()
+ }
+ }
+
+ companion object {
+ const val sPipWindowTitle = "PipMenuActivity"
+
+ @AfterClass
+ @JvmStatic
+ fun teardown() {
+ val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ if (AutomationUtils.hasPipWindow(device)) {
+ AutomationUtils.closePipWindow(device)
+ }
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToAppTest.kt
new file mode 100644
index 0000000..bfececa
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToAppTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 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.pip
+
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.LargeTest
+import com.android.server.wm.flicker.CommonTransitions
+import com.android.server.wm.flicker.TransitionRunner
+import com.android.server.wm.flicker.WmTraceSubject
+import com.android.server.wm.flicker.helpers.PipAppHelper
+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 Pip launch.
+ * To run this test: `atest FlickerTests:PipToAppTest`
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 152738416)
+@Ignore("Waiting bug feedback")
+class PipToAppTest(
+ beginRotationName: String,
+ beginRotation: Int
+) : PipTestBase(beginRotationName, beginRotation) {
+ override val transitionToRun: TransitionRunner
+ get() = CommonTransitions.exitPipModeToApp(testApp as PipAppHelper, instrumentation,
+ uiDevice, beginRotation)
+ .includeJankyRuns().build()
+
+ @Test
+ fun checkVisibility_backgroundWindowVisibleBehindPipLayer() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .skipUntilFirstAssertion()
+ .showsAppWindowOnTop(sPipWindowTitle)
+ .then()
+ .showsBelowAppWindow("Wallpaper")
+ .then()
+ .showsAppWindowOnTop(testApp.getPackage())
+ .then()
+ .hidesAppWindowOnTop(testApp.getPackage())
+ .forAllEntries()
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToHomeTest.kt
new file mode 100644
index 0000000..ecfcd82
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToHomeTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 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.pip
+
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.LargeTest
+import com.android.server.wm.flicker.CommonTransitions
+import com.android.server.wm.flicker.TransitionRunner
+import com.android.server.wm.flicker.WmTraceSubject
+import com.android.server.wm.flicker.helpers.PipAppHelper
+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 Pip launch.
+ * To run this test: `atest FlickerTests:PipToHomeTest`
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 152738416)
+@Ignore("Waiting bug feedback")
+class PipToHomeTest(
+ beginRotationName: String,
+ beginRotation: Int
+) : PipTestBase(beginRotationName, beginRotation) {
+ override val transitionToRun: TransitionRunner
+ get() = CommonTransitions.exitPipModeToHome(testApp as PipAppHelper, instrumentation,
+ uiDevice, beginRotation)
+ .includeJankyRuns().build()
+
+ @Ignore
+ @Test
+ fun checkVisibility_backgroundWindowVisibleBehindPipLayer() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAppWindowOnTop(sPipWindowTitle)
+ .then()
+ .showsBelowAppWindow("Wallpaper")
+ .then()
+ .showsAppWindowOnTop("Wallpaper")
+ .forAllEntries()
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
new file mode 100644
index 0000000..7a581d0
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 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.rotation
+
+import android.util.Log
+import androidx.test.filters.LargeTest
+import com.android.server.wm.flicker.CommonTransitions
+import com.android.server.wm.flicker.LayersTraceSubject
+import com.android.server.wm.flicker.RotationTestBase
+import com.android.server.wm.flicker.StandardAppHelper
+import com.android.server.wm.flicker.TransitionRunner
+import com.android.server.wm.flicker.WindowUtils
+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
+
+/**
+ * Cycle through supported app rotations.
+ * To run this test: `atest FlickerTest:ChangeAppRotationTest`
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ChangeAppRotationTest(
+ beginRotationName: String,
+ endRotationName: String,
+ beginRotation: Int,
+ endRotation: Int
+) : RotationTestBase(beginRotationName, endRotationName, beginRotation, endRotation) {
+ init {
+ testApp = StandardAppHelper(instrumentation,
+ "com.android.server.wm.flicker.testapp", "SimpleApp")
+ }
+
+ override val transitionToRun: TransitionRunner
+ get() = CommonTransitions.changeAppRotation(testApp, instrumentation, uiDevice,
+ beginRotation, endRotation)
+ .includeJankyRuns().build()
+
+ @Test
+ fun checkPosition_appLayerRotates() {
+ val startingPos = WindowUtils.getAppPosition(beginRotation)
+ val endingPos = WindowUtils.getAppPosition(endRotation)
+ Log.e(TAG, "startingPos=$startingPos endingPos=$endingPos")
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .hasVisibleRegion(testApp.getPackage(), startingPos).inTheBeginning()
+ LayersTraceSubject.assertThat(it)
+ .hasVisibleRegion(testApp.getPackage(), endingPos).atTheEnd()
+ }
+ }
+
+ @Ignore("Flaky. Pending debug")
+ @Test
+ fun checkVisibility_screenshotLayerBecomesInvisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .showsLayer(testApp.getPackage())
+ .then()
+ .replaceVisibleLayer(testApp.getPackage(), SCREENSHOT_LAYER)
+ .then()
+ .showsLayer(testApp.getPackage()).and().showsLayer(SCREENSHOT_LAYER)
+ .then()
+ .replaceVisibleLayer(SCREENSHOT_LAYER, testApp.getPackage())
+ .forAllEntries()
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
new file mode 100644
index 0000000..d53af6f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 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.rotation
+
+import android.content.Intent
+import android.view.Surface
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.LargeTest
+import com.android.server.wm.flicker.CommonTransitions
+import com.android.server.wm.flicker.LayersTraceSubject
+import com.android.server.wm.flicker.RotationTestBase
+import com.android.server.wm.flicker.TransitionRunner
+import com.android.server.wm.flicker.WindowUtils
+import com.android.server.wm.flicker.testapp.ActivityOptions
+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
+
+/**
+ * Cycle through supported app rotations using seamless rotations.
+ * To run this test: `atest FlickerTests:SeamlessAppRotationTest`
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 147659548)
+@Ignore("Waiting bug feedback")
+class SeamlessAppRotationTest(
+ private val intent: Intent,
+ beginRotationName: String,
+ endRotationName: String,
+ beginRotation: Int,
+ endRotation: Int
+) : RotationTestBase(beginRotationName, endRotationName, beginRotation, endRotation) {
+ override val transitionToRun: TransitionRunner
+ get() {
+ var intentId = ""
+ if (intent.extras?.getBoolean(ActivityOptions.EXTRA_STARVE_UI_THREAD) == true) {
+ intentId = "BUSY_UI_THREAD"
+ }
+ return CommonTransitions.changeAppRotation(intent, intentId,
+ InstrumentationRegistry.getContext(), instrumentation, uiDevice,
+ beginRotation, endRotation).build()
+ }
+
+ @Test
+ fun checkPosition_appLayerRotates() {
+ val startingPos = WindowUtils.getAppPosition(beginRotation)
+ val endingPos = WindowUtils.getAppPosition(endRotation)
+ if (startingPos == endingPos) {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .hasVisibleRegion(intent.component?.packageName ?: "", startingPos)
+ .forAllEntries()
+ }
+ } else {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .hasVisibleRegion(intent.component?.packageName ?: "", startingPos)
+ .then()
+ .hasVisibleRegion(intent.component?.packageName ?: "", endingPos)
+ .forAllEntries()
+ }
+ }
+ }
+
+ @Test
+ fun checkCoveredRegion_noUncoveredRegions() {
+ val startingBounds = WindowUtils.getDisplayBounds(beginRotation)
+ val endingBounds = WindowUtils.getDisplayBounds(endRotation)
+ if (startingBounds == endingBounds) {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .coversRegion(startingBounds)
+ .forAllEntries()
+ }
+ } else {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .coversRegion(startingBounds)
+ .then()
+ .coversRegion(endingBounds)
+ .forAllEntries()
+ }
+ }
+ }
+
+ companion object {
+ // launch test activity that supports seamless rotation
+
+ // launch test activity that supports seamless rotation with a busy UI thread to miss frames
+ // when the app is asked to redraw
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
+ val params: MutableCollection<Array<Any>> = ArrayList()
+ val testIntents = ArrayList<Intent>()
+
+ // launch test activity that supports seamless rotation
+ var intent = Intent(Intent.ACTION_MAIN)
+ intent.component = ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME
+ testIntents.add(intent)
+
+ // launch test activity that supports seamless rotation with a busy UI thread to miss frames
+ // when the app is asked to redraw
+ intent = Intent(intent)
+ intent.putExtra(ActivityOptions.EXTRA_STARVE_UI_THREAD, true)
+ testIntents.add(intent)
+ for (testIntent in testIntents) {
+ for (begin in supportedRotations) {
+ for (end in supportedRotations) {
+ if (begin != end) {
+ var testId: String = Surface.rotationToString(begin) +
+ "_" + Surface.rotationToString(end)
+ if (testIntent.extras?.getBoolean(
+ ActivityOptions.EXTRA_STARVE_UI_THREAD) == true) {
+ testId += "_" + "BUSY_UI_THREAD"
+ }
+ params.add(arrayOf(testId, testIntent, begin, end))
+ }
+ }
+ }
+ }
+ return params
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt
new file mode 100644
index 0000000..b5611a4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 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.splitscreen
+
+import androidx.test.filters.LargeTest
+import com.android.server.wm.flicker.CommonTransitions
+import com.android.server.wm.flicker.LayersTraceSubject
+import com.android.server.wm.flicker.NonRotationTestBase
+import com.android.server.wm.flicker.StandardAppHelper
+import com.android.server.wm.flicker.TransitionRunner
+import com.android.server.wm.flicker.WmTraceSubject
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest FlickerTests:OpenAppToSplitScreenTest`
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppToSplitScreenTest(
+ beginRotationName: String,
+ beginRotation: Int
+) : NonRotationTestBase(beginRotationName, beginRotation) {
+ init {
+ testApp = StandardAppHelper(instrumentation,
+ "com.android.server.wm.flicker.testapp", "SimpleApp")
+ }
+
+ override val transitionToRun: TransitionRunner
+ get() = CommonTransitions.appToSplitScreen(testApp, instrumentation, uiDevice,
+ beginRotation).includeJankyRuns().build()
+
+ @Test
+ fun checkVisibility_navBarWindowIsAlwaysVisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()
+ }
+ }
+
+ @Test
+ fun checkVisibility_statusBarWindowIsAlwaysVisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries()
+ }
+ }
+
+ @Test
+ fun checkVisibility_dividerLayerBecomesVisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .hidesLayer(DOCKED_STACK_DIVIDER)
+ .then()
+ .showsLayer(DOCKED_STACK_DIVIDER)
+ .forAllEntries()
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/ResizeSplitScreenTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/ResizeSplitScreenTest.kt
new file mode 100644
index 0000000..b6cce26
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/ResizeSplitScreenTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2020 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.splitscreen
+
+import android.graphics.Region
+import android.util.Rational
+import android.view.Surface
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.LargeTest
+import androidx.test.runner.AndroidJUnit4
+import androidx.test.uiautomator.UiDevice
+import com.android.server.wm.flicker.CommonTransitions
+import com.android.server.wm.flicker.FlickerTestBase
+import com.android.server.wm.flicker.LayersTrace
+import com.android.server.wm.flicker.LayersTraceSubject
+import com.android.server.wm.flicker.StandardAppHelper
+import com.android.server.wm.flicker.TransitionRunner
+import com.android.server.wm.flicker.TransitionResult
+import com.android.server.wm.flicker.WindowUtils
+import com.android.server.wm.flicker.WmTraceSubject
+import com.android.server.wm.flicker.helpers.AutomationUtils
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.google.common.truth.Truth
+import org.junit.AfterClass
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+
+/**
+ * Test split screen resizing window transitions.
+ * To run this test: `atest FlickerTests:ResizeSplitScreenTest`
+ *
+ * Currently it runs only in 0 degrees because of b/156100803
+ */
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 159096424)
+@Ignore("Waiting bug feedback")
+class ResizeSplitScreenTest : FlickerTestBase() {
+ init {
+ testApp = StandardAppHelper(instrumentation,
+ "com.android.server.wm.flicker.testapp", "SimpleApp")
+ }
+
+ override val transitionToRun: TransitionRunner
+ get() {
+ val bottomApp = ImeAppHelper(instrumentation)
+ return CommonTransitions.resizeSplitScreen(testApp, bottomApp, instrumentation,
+ uiDevice, Surface.ROTATION_0,
+ Rational(1, 3), Rational(2, 3))
+ .includeJankyRuns().build()
+ }
+
+ @Test
+ fun checkVisibility_topAppLayerIsAlwaysVisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .showsLayer(sSimpleActivity)
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ fun checkVisibility_bottomAppLayerIsAlwaysVisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .showsLayer(sImeActivity)
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ fun checkVisibility_dividerLayerIsAlwaysVisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .showsLayer(DOCKED_STACK_DIVIDER)
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ @Ignore("Waiting feedback")
+ fun checkPosition_appsStartingBounds() {
+ val displayBounds = WindowUtils.getDisplayBounds()
+ checkResults { result: TransitionResult ->
+ val entries = LayersTrace.parseFrom(result.layersTrace,
+ result.layersTracePath, result.layersTraceChecksum)
+ Truth.assertThat(entries.entries).isNotEmpty()
+ val startingDividerBounds = entries.entries[0].getVisibleBounds(
+ DOCKED_STACK_DIVIDER).bounds
+ val startingTopAppBounds = Region(0, 0, startingDividerBounds.right,
+ startingDividerBounds.top + WindowUtils.getDockedStackDividerInset())
+ val startingBottomAppBounds = Region(0,
+ startingDividerBounds.bottom - WindowUtils.getDockedStackDividerInset(),
+ displayBounds.right,
+ displayBounds.bottom - WindowUtils.getNavigationBarHeight())
+ LayersTraceSubject.assertThat(result)
+ .hasVisibleRegion("SimpleActivity", startingTopAppBounds)
+ .inTheBeginning()
+ LayersTraceSubject.assertThat(result)
+ .hasVisibleRegion("ImeActivity", startingBottomAppBounds)
+ .inTheBeginning()
+ }
+ }
+
+ @Test
+ @Ignore("Waiting feedback")
+ fun checkPosition_appsEndingBounds() {
+ val displayBounds = WindowUtils.getDisplayBounds()
+ checkResults { result: TransitionResult ->
+ val entries = LayersTrace.parseFrom(result.layersTrace,
+ result.layersTracePath, result.layersTraceChecksum)
+ Truth.assertThat(entries.entries).isNotEmpty()
+ val endingDividerBounds = entries.entries[entries.entries.size - 1].getVisibleBounds(
+ DOCKED_STACK_DIVIDER).bounds
+ val startingTopAppBounds = Region(0, 0, endingDividerBounds.right,
+ endingDividerBounds.top + WindowUtils.getDockedStackDividerInset())
+ val startingBottomAppBounds = Region(0,
+ endingDividerBounds.bottom - WindowUtils.getDockedStackDividerInset(),
+ displayBounds.right,
+ displayBounds.bottom - WindowUtils.getNavigationBarHeight())
+ LayersTraceSubject.assertThat(result)
+ .hasVisibleRegion(sSimpleActivity, startingTopAppBounds)
+ .atTheEnd()
+ LayersTraceSubject.assertThat(result)
+ .hasVisibleRegion(sImeActivity, startingBottomAppBounds)
+ .atTheEnd()
+ }
+ }
+
+ @Test
+ fun checkVisibility_navBarWindowIsAlwaysVisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE)
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ fun checkVisibility_statusBarWindowIsAlwaysVisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE)
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ @FlakyTest(bugId = 156223549)
+ @Ignore("Waiting bug feedback")
+ fun checkVisibility_topAppWindowIsAlwaysVisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAppWindow(sSimpleActivity)
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ @FlakyTest(bugId = 156223549)
+ @Ignore("Waiting bug feedback")
+ fun checkVisibility_bottomAppWindowIsAlwaysVisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAppWindow(sImeActivity)
+ .forAllEntries()
+ }
+ }
+
+ companion object {
+ private const val sSimpleActivity = "SimpleActivity"
+ private const val sImeActivity = "ImeActivity"
+
+ @AfterClass
+ @JvmStatic
+ fun teardown() {
+ val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ if (AutomationUtils.isInSplitScreen(device)) {
+ AutomationUtils.exitSplitScreen(device)
+ }
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt
new file mode 100644
index 0000000..fdcafdb
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 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.splitscreen
+
+import android.view.Surface
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.LargeTest
+import androidx.test.runner.AndroidJUnit4
+import androidx.test.uiautomator.UiDevice
+import com.android.server.wm.flicker.CommonTransitions
+import com.android.server.wm.flicker.FlickerTestBase
+import com.android.server.wm.flicker.LayersTraceSubject
+import com.android.server.wm.flicker.StandardAppHelper
+import com.android.server.wm.flicker.TransitionRunner
+import com.android.server.wm.flicker.WindowUtils
+import com.android.server.wm.flicker.WmTraceSubject
+import com.android.server.wm.flicker.helpers.AutomationUtils
+import org.junit.AfterClass
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest FlickerTests:SplitScreenToLauncherTest`
+ */
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SplitScreenToLauncherTest : FlickerTestBase() {
+ init {
+ testApp = StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
+ "com.android.server.wm.flicker.testapp", "SimpleApp")
+ }
+
+ override val transitionToRun: TransitionRunner
+ get() = CommonTransitions.splitScreenToLauncher(testApp, instrumentation, uiDevice,
+ Surface.ROTATION_0).includeJankyRuns().build()
+
+ @Test
+ fun checkCoveredRegion_noUncoveredRegions() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .coversRegion(WindowUtils.getDisplayBounds()).forAllEntries()
+ }
+ }
+
+ @Test
+ fun checkVisibility_dividerLayerBecomesInVisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .showsLayer(DOCKED_STACK_DIVIDER)
+ .then()
+ .hidesLayer(DOCKED_STACK_DIVIDER)
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ fun checkVisibility_appLayerBecomesInVisible() {
+ checkResults {
+ LayersTraceSubject.assertThat(it)
+ .showsLayer(testApp.getPackage())
+ .then()
+ .hidesLayer(testApp.getPackage())
+ .forAllEntries()
+ }
+ }
+
+ @Test
+ fun checkVisibility_navBarWindowIsAlwaysVisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()
+ }
+ }
+
+ @Test
+ fun checkVisibility_statusBarWindowIsAlwaysVisible() {
+ checkResults {
+ WmTraceSubject.assertThat(it)
+ .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries()
+ }
+ }
+
+ companion object {
+ @AfterClass
+ @JvmStatic
+ fun teardown() {
+ val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ if (AutomationUtils.isInSplitScreen(device)) {
+ AutomationUtils.exitSplitScreen(device)
+ }
+ }
+ }
+}
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index 65d15a7..da6018e 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -24,7 +24,16 @@
name: "StagedInstallInternalTest",
srcs: ["src/**/*.java"],
libs: ["tradefed"],
- static_libs: ["testng", "compatibility-tradefed", "frameworks-base-hostutils"],
+ static_libs: [
+ "testng",
+ "compatibility-tradefed",
+ "frameworks-base-hostutils",
+ "module_test_util",
+ ],
+ data: [
+ ":com.android.apex.cts.shim.v2_prebuilt",
+ ":TestAppAv1",
+ ],
test_suites: ["general-tests"],
test_config: "StagedInstallInternalTest.xml",
}
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index e6ba801..7cfbdc2 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -20,9 +20,11 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import com.android.ddmlib.Log;
import com.android.tests.rollback.host.AbandonSessionsRule;
+import com.android.tests.util.ModuleTestUtils;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.ProcessInfo;
@@ -33,6 +35,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
+
@RunWith(DeviceJUnit4ClassRunner.class)
public class StagedInstallInternalTest extends BaseHostJUnit4Test {
@@ -41,6 +45,10 @@
@Rule
public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this);
+ private static final String SHIM_V2 = "com.android.apex.cts.shim.v2.apex";
+ private static final String APK_A = "TestAppAv1.apk";
+
+ private final ModuleTestUtils mTestUtils = new ModuleTestUtils(this);
/**
* Runs the given phase of a test by calling into the device.
@@ -82,6 +90,58 @@
runPhase("testSystemServerRestartDoesNotAffectStagedSessions_Verify");
}
+ @Test
+ public void testAdbStagedInstallWaitForReadyFlagWorks() throws Exception {
+ assumeTrue("Device does not support updating APEX",
+ mTestUtils.isApexUpdateSupported());
+
+ File apexFile = mTestUtils.getTestFile(SHIM_V2);
+ String output = getDevice().executeAdbCommand("install", "--staged",
+ "--wait-for-staged-ready", "60000", apexFile.getAbsolutePath());
+ assertThat(output).contains("Reboot device to apply staged session");
+ String sessionId = getDevice().executeShellCommand(
+ "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim();
+ assertThat(sessionId).isNotEmpty();
+ }
+
+ @Test
+ public void testAdbStagedInstallNoWaitFlagWorks() throws Exception {
+ assumeTrue("Device does not support updating APEX",
+ mTestUtils.isApexUpdateSupported());
+
+ File apexFile = mTestUtils.getTestFile(SHIM_V2);
+ String output = getDevice().executeAdbCommand("install", "--staged",
+ "--no-wait", apexFile.getAbsolutePath());
+ assertThat(output).doesNotContain("Reboot device to apply staged session");
+ assertThat(output).contains("Success");
+ String sessionId = getDevice().executeShellCommand(
+ "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim();
+ assertThat(sessionId).isEmpty();
+ }
+
+ @Test
+ public void testAdbInstallMultiPackageCommandWorks() throws Exception {
+ assumeTrue("Device does not support updating APEX",
+ mTestUtils.isApexUpdateSupported());
+
+ File apexFile = mTestUtils.getTestFile(SHIM_V2);
+ File apkFile = mTestUtils.getTestFile(APK_A);
+ String output = getDevice().executeAdbCommand("install-multi-package",
+ apexFile.getAbsolutePath(), apkFile.getAbsolutePath());
+ assertThat(output).contains("Created parent session");
+ assertThat(output).contains("Created child session");
+ assertThat(output).contains("Success. Reboot device to apply staged session");
+
+ // Ensure there is only one parent session
+ String[] sessionIds = getDevice().executeShellCommand(
+ "pm get-stagedsessions --only-ready --only-parent --only-sessionid").split("\n");
+ assertThat(sessionIds.length).isEqualTo(1);
+ // Ensure there are two children session
+ sessionIds = getDevice().executeShellCommand(
+ "pm get-stagedsessions --only-ready --only-sessionid").split("\n");
+ assertThat(sessionIds.length).isEqualTo(3);
+ }
+
private void restartSystemServer() throws Exception {
// Restart the system server
ProcessInfo oldPs = getDevice().getProcessByName("system_server");
diff --git a/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
index 9adbf21..16fed39 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
@@ -17,7 +17,6 @@
package com.android.server.net;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
@@ -32,7 +31,6 @@
import android.annotation.NonNull;
import android.content.Context;
import android.os.test.TestLooper;
-import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
@@ -63,6 +61,7 @@
private static final String TEST_IMSI3 = "466929999999999";
@Mock private Context mContext;
+ @Mock private PhoneStateListener mPhoneStateListener;
@Mock private SubscriptionManager mSubscriptionManager;
@Mock private TelephonyManager mTelephonyManager;
@Mock private NetworkStatsSubscriptionsMonitor.Delegate mDelegate;
@@ -216,53 +215,4 @@
verify(mTelephonyManager, times(2)).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
}
-
- @Test
- public void test5g() {
- mMonitor.start();
- // Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback
- // before changing RAT type. Also capture listener for later use.
- addTestSub(TEST_SUBID1, TEST_IMSI1);
- assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
- final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor =
- ArgumentCaptor.forClass(RatTypeListener.class);
- verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(),
- eq(PhoneStateListener.LISTEN_SERVICE_STATE));
- final RatTypeListener listener = CollectionUtils
- .find(ratTypeListenerCaptor.getAllValues(), it -> it.getSubId() == TEST_SUBID1);
- assertNotNull(listener);
-
- // Set RAT type to 5G NSA (non-standalone) mode, verify the monitor outputs NETWORK_TYPE_NR.
- final ServiceState serviceState = mock(ServiceState.class);
- when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE);
- when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED);
- listener.onServiceStateChanged(serviceState);
- assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_NR);
- reset(mDelegate);
-
- // Set RAT type to LTE without NR connected, the RAT type should be downgraded to LTE.
- when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_NONE);
- listener.onServiceStateChanged(serviceState);
- assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_LTE);
- reset(mDelegate);
-
- // Verify NR connected with other RAT type does not take effect.
- when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_UMTS);
- when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED);
- listener.onServiceStateChanged(serviceState);
- assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
- reset(mDelegate);
-
- // Set RAT type to 5G standalone mode, the RAT type should be NR.
- setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
- TelephonyManager.NETWORK_TYPE_NR);
- assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_NR);
- reset(mDelegate);
-
- // Set NR state to none in standalone mode does not change anything.
- when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_NR);
- when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_NONE);
- listener.onServiceStateChanged(serviceState);
- assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_NR);
- }
}
diff --git a/tools/validatekeymaps/Android.bp b/tools/validatekeymaps/Android.bp
index 819e75b..61ce44c 100644
--- a/tools/validatekeymaps/Android.bp
+++ b/tools/validatekeymaps/Android.bp
@@ -20,6 +20,7 @@
"libutils",
"libcutils",
"liblog",
+ "libui-types",
],
// This tool is prebuilt if we're doing an app-only build.