diff options
117 files changed, 8354 insertions, 1833 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index ce311d01cd58..091622715578 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -74,6 +74,7 @@ aconfig_srcjars = [ ":android.chre.flags-aconfig-java{.generated_srcjars}", ":android.speech.flags-aconfig-java{.generated_srcjars}", ":power_flags_lib{.generated_srcjars}", + ":android.content.flags-aconfig-java{.generated_srcjars}", ] filegroup { @@ -940,3 +941,16 @@ java_aconfig_library { aconfig_declarations: "power_flags", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Content +aconfig_declarations { + name: "android.content.flags-aconfig", + package: "android.content.flags", + srcs: ["core/java/android/content/flags/flags.aconfig"], +} + +java_aconfig_library { + name: "android.content.flags-aconfig-java", + aconfig_declarations: "android.content.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Android.bp b/Android.bp index a3e39018e147..485f1be42017 100644 --- a/Android.bp +++ b/Android.bp @@ -149,6 +149,7 @@ filegroup { ":framework-javastream-protos", ":statslog-framework-java-gen", // FrameworkStatsLog.java ":audio_policy_configuration_V7_0", + ":perfetto_trace_javastream_protos", ], } diff --git a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java index caf7e7f4a4ed..1fc888b06ffd 100644 --- a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java @@ -16,6 +16,7 @@ package com.android.server; +import android.annotation.NonNull; import android.annotation.Nullable; import android.os.PowerExemptionManager; import android.os.PowerExemptionManager.ReasonCode; @@ -77,6 +78,9 @@ public interface DeviceIdleInternal { int[] getPowerSaveTempWhitelistAppIds(); + @NonNull + String[] getFullPowerWhitelistExceptIdle(); + /** * Listener to be notified when DeviceIdleController determines that the device has moved or is * stationary. diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 6383ed873e59..31214cbb7066 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -2374,6 +2374,11 @@ public class DeviceIdleController extends SystemService return DeviceIdleController.this.isAppOnWhitelistInternal(appid); } + @Override + public String[] getFullPowerWhitelistExceptIdle() { + return DeviceIdleController.this.getFullPowerWhitelistInternalUnchecked(); + } + /** * Returns the array of app ids whitelisted by user. Take care not to * modify this, as it is a reference to the original copy. But the reference @@ -3100,10 +3105,14 @@ public class DeviceIdleController extends SystemService } private String[] getFullPowerWhitelistInternal(final int callingUid, final int callingUserId) { - final String[] apps; + return ArrayUtils.filter(getFullPowerWhitelistInternalUnchecked(), String[]::new, + (pkg) -> !mPackageManagerInternal.filterAppAccess(pkg, callingUid, callingUserId)); + } + + private String[] getFullPowerWhitelistInternalUnchecked() { synchronized (this) { int size = mPowerSaveWhitelistApps.size() + mPowerSaveWhitelistUserApps.size(); - apps = new String[size]; + final String[] apps = new String[size]; int cur = 0; for (int i = 0; i < mPowerSaveWhitelistApps.size(); i++) { apps[cur] = mPowerSaveWhitelistApps.keyAt(i); @@ -3113,9 +3122,8 @@ public class DeviceIdleController extends SystemService apps[cur] = mPowerSaveWhitelistUserApps.keyAt(i); cur++; } + return apps; } - return ArrayUtils.filter(apps, String[]::new, - (pkg) -> !mPackageManagerInternal.filterAppAccess(pkg, callingUid, callingUserId)); } public boolean isPowerSaveWhitelistExceptIdleAppInternal(String packageName) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java index e3ba50dc635b..e3ac780abf09 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java @@ -318,6 +318,10 @@ public final class BackgroundJobsController extends StateController { try { final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid); + if (DEBUG) { + Slog.d(TAG, + "Pulled stopped state of " + packageName + " (" + uid + "): " + isStopped); + } mPackageStoppedState.add(uid, packageName, isStopped); return isStopped; } catch (PackageManager.NameNotFoundException e) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index 0e67b9ac944f..2c9af67ecdc4 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -30,11 +30,15 @@ import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.job.JobInfo; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.PowerManager; import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.ArraySet; @@ -48,6 +52,8 @@ import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.AppSchedulingModuleThread; +import com.android.server.DeviceIdleInternal; +import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; import com.android.server.utils.AlarmQueue; @@ -127,6 +133,19 @@ public final class FlexibilityController extends StateController { @GuardedBy("mLock") private final SparseLongArray mLastSeenConstraintTimesElapsed = new SparseLongArray(); + private DeviceIdleInternal mDeviceIdleInternal; + private final ArraySet<String> mPowerAllowlistedApps = new ArraySet<>(); + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: + mHandler.post(FlexibilityController.this::updatePowerAllowlistCache); + break; + } + } + }; @VisibleForTesting @GuardedBy("mLock") final FlexibilityTracker mFlexibilityTracker; @@ -180,8 +199,16 @@ public final class FlexibilityController extends StateController { } }; - private static final int MSG_UPDATE_JOBS = 0; - private static final int MSG_UPDATE_JOB = 1; + private static final int MSG_CHECK_ALL_JOBS = 0; + /** Check the jobs in {@link #mJobsToCheck} */ + private static final int MSG_CHECK_JOBS = 1; + /** Check the jobs of packages in {@link #mPackagesToCheck} */ + private static final int MSG_CHECK_PACKAGES = 2; + + @GuardedBy("mLock") + private final ArraySet<JobStatus> mJobsToCheck = new ArraySet<>(); + @GuardedBy("mLock") + private final ArraySet<String> mPackagesToCheck = new ArraySet<>(); public FlexibilityController( JobSchedulerService service, PrefetchController prefetchController) { @@ -204,6 +231,16 @@ public final class FlexibilityController extends StateController { mPercentToDropConstraints = mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS; mPrefetchController = prefetchController; + + if (mFlexibilityEnabled) { + registerBroadcastReceiver(); + } + } + + @Override + public void onSystemServicesReady() { + mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class); + mHandler.post(FlexibilityController.this::updatePowerAllowlistCache); } @Override @@ -241,6 +278,7 @@ public final class FlexibilityController extends StateController { mFlexibilityAlarmQueue.removeAlarmForKey(js); mFlexibilityTracker.remove(js); } + mJobsToCheck.remove(js); } @Override @@ -266,7 +304,14 @@ public final class FlexibilityController extends StateController { @GuardedBy("mLock") boolean isFlexibilitySatisfiedLocked(JobStatus js) { return !mFlexibilityEnabled + // Exclude all jobs of the TOP app || mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP + // Only exclude DEFAULT+ priority jobs for BFGS+ apps + || (mService.getUidBias(js.getSourceUid()) >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE + && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT) + // For apps in the power allowlist, automatically exclude DEFAULT+ priority jobs. + || (js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT + && mPowerAllowlistedApps.contains(js.getSourcePackageName())) || hasEnoughSatisfiedConstraintsLocked(js) || mService.isCurrentlyRunningLocked(js); } @@ -371,7 +416,7 @@ public final class FlexibilityController extends StateController { // Push the job update to the handler to avoid blocking other controllers and // potentially batch back-to-back controller state updates together. - mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget(); + mHandler.obtainMessage(MSG_CHECK_ALL_JOBS).sendToTarget(); } } } @@ -485,7 +530,9 @@ public final class FlexibilityController extends StateController { @Override @GuardedBy("mLock") public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) { - if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) { + if (prevBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE + && newBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE) { + // All changes are below BFGS. There's no significant change to care about. return; } final long nowElapsed = sElapsedRealtimeClock.millis(); @@ -557,6 +604,39 @@ public final class FlexibilityController extends StateController { mFcConfig.processConstantLocked(properties, key); } + private void registerBroadcastReceiver() { + IntentFilter filter = new IntentFilter(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); + mContext.registerReceiver(mBroadcastReceiver, filter); + } + + private void unregisterBroadcastReceiver() { + mContext.unregisterReceiver(mBroadcastReceiver); + } + + private void updatePowerAllowlistCache() { + if (mDeviceIdleInternal == null) { + return; + } + + // Don't call out to DeviceIdleController with the lock held. + final String[] allowlistedPkgs = mDeviceIdleInternal.getFullPowerWhitelistExceptIdle(); + final ArraySet<String> changedPkgs = new ArraySet<>(); + synchronized (mLock) { + changedPkgs.addAll(mPowerAllowlistedApps); + mPowerAllowlistedApps.clear(); + for (final String pkgName : allowlistedPkgs) { + mPowerAllowlistedApps.add(pkgName); + if (changedPkgs.contains(pkgName)) { + changedPkgs.remove(pkgName); + } else { + changedPkgs.add(pkgName); + } + } + mPackagesToCheck.addAll(changedPkgs); + mHandler.sendEmptyMessage(MSG_CHECK_PACKAGES); + } + } + @VisibleForTesting class FlexibilityTracker { final ArrayList<ArraySet<JobStatus>> mTrackedJobs; @@ -710,7 +790,8 @@ public final class FlexibilityController extends StateController { } mFlexibilityTracker.setNumDroppedFlexibleConstraints(js, js.getNumAppliedFlexibleConstraints()); - mHandler.obtainMessage(MSG_UPDATE_JOB, js).sendToTarget(); + mJobsToCheck.add(js); + mHandler.sendEmptyMessage(MSG_CHECK_JOBS); return; } if (nextTimeElapsed == NO_LIFECYCLE_END) { @@ -761,10 +842,12 @@ public final class FlexibilityController extends StateController { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_UPDATE_JOBS: - removeMessages(MSG_UPDATE_JOBS); + case MSG_CHECK_ALL_JOBS: + removeMessages(MSG_CHECK_ALL_JOBS); synchronized (mLock) { + mJobsToCheck.clear(); + mPackagesToCheck.clear(); final long nowElapsed = sElapsedRealtimeClock.millis(); final ArraySet<JobStatus> changedJobs = new ArraySet<>(); @@ -790,19 +873,50 @@ public final class FlexibilityController extends StateController { } break; - case MSG_UPDATE_JOB: + case MSG_CHECK_JOBS: synchronized (mLock) { - final JobStatus js = (JobStatus) msg.obj; - if (DEBUG) { - Slog.d("blah", "Checking on " + js.toShortString()); + final long nowElapsed = sElapsedRealtimeClock.millis(); + ArraySet<JobStatus> changedJobs = new ArraySet<>(); + + for (int i = mJobsToCheck.size() - 1; i >= 0; --i) { + final JobStatus js = mJobsToCheck.valueAt(i); + if (DEBUG) { + Slog.d(TAG, "Checking on " + js.toShortString()); + } + if (js.setFlexibilityConstraintSatisfied( + nowElapsed, isFlexibilitySatisfiedLocked(js))) { + changedJobs.add(js); + } + } + + mJobsToCheck.clear(); + if (changedJobs.size() > 0) { + mStateChangedListener.onControllerStateChanged(changedJobs); } + } + break; + + case MSG_CHECK_PACKAGES: + synchronized (mLock) { final long nowElapsed = sElapsedRealtimeClock.millis(); - if (js.setFlexibilityConstraintSatisfied( - nowElapsed, isFlexibilitySatisfiedLocked(js))) { - // TODO(141645789): add method that will take a single job - ArraySet<JobStatus> changedJob = new ArraySet<>(); - changedJob.add(js); - mStateChangedListener.onControllerStateChanged(changedJob); + final ArraySet<JobStatus> changedJobs = new ArraySet<>(); + + mService.getJobStore().forEachJob( + (js) -> mPackagesToCheck.contains(js.getSourcePackageName()) + || mPackagesToCheck.contains(js.getCallingPackageName()), + (js) -> { + if (DEBUG) { + Slog.d(TAG, "Checking on " + js.toShortString()); + } + if (js.setFlexibilityConstraintSatisfied( + nowElapsed, isFlexibilitySatisfiedLocked(js))) { + changedJobs.add(js); + } + }); + + mPackagesToCheck.clear(); + if (changedJobs.size() > 0) { + mStateChangedListener.onControllerStateChanged(changedJobs); } } break; @@ -882,10 +996,12 @@ public final class FlexibilityController extends StateController { mFlexibilityEnabled = true; mPrefetchController .registerPrefetchChangedListener(mPrefetchChangedListener); + registerBroadcastReceiver(); } else { mFlexibilityEnabled = false; mPrefetchController .unRegisterPrefetchChangedListener(mPrefetchChangedListener); + unregisterBroadcastReceiver(); } } break; @@ -985,7 +1101,14 @@ public final class FlexibilityController extends StateController { pw.println(":"); pw.increaseIndent(); - pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS).println(); + pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS); + pw.print("("); + if (APPLIED_CONSTRAINTS != 0) { + JobStatus.dumpConstraints(pw, APPLIED_CONSTRAINTS); + } else { + pw.print("nothing"); + } + pw.println(")"); pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println(); pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println(); pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS, @@ -1044,6 +1167,10 @@ public final class FlexibilityController extends StateController { pw.decreaseIndent(); pw.println(); + pw.print("Power allowlisted packages: "); + pw.println(mPowerAllowlistedApps); + + pw.println(); mFlexibilityTracker.dump(pw, predicate); pw.println(); mFlexibilityAlarmQueue.dump(pw); diff --git a/boot/Android.bp b/boot/Android.bp index 8a3d35e2d0eb..c4a1139b70a4 100644 --- a/boot/Android.bp +++ b/boot/Android.bp @@ -26,9 +26,10 @@ package { soong_config_module_type { name: "custom_platform_bootclasspath", module_type: "platform_bootclasspath", - config_namespace: "AUTO", + config_namespace: "bootclasspath", bool_variables: [ "car_bootclasspath_fragment", + "nfc_apex_bootclasspath_fragment", ], properties: [ "fragments", @@ -155,6 +156,15 @@ custom_platform_bootclasspath { }, ], }, + nfc_apex_bootclasspath_fragment: { + fragments: [ + // only used if NFC mainline is enabled. + { + apex: "com.android.nfcservices", + module: "com.android.nfcservices-bootclasspath-fragment", + }, + ], + }, }, // Additional information needed by hidden api processing. diff --git a/boot/hiddenapi/hiddenapi-max-target-o.txt b/boot/hiddenapi/hiddenapi-max-target-o.txt index 3c16915cf71f..2d417ce752e1 100644 --- a/boot/hiddenapi/hiddenapi-max-target-o.txt +++ b/boot/hiddenapi/hiddenapi-max-target-o.txt @@ -33834,649 +33834,6 @@ Landroid/net/WifiLinkQualityInfo;->setRssi(I)V Landroid/net/WifiLinkQualityInfo;->setTxBad(J)V Landroid/net/WifiLinkQualityInfo;->setTxGood(J)V Landroid/net/WifiLinkQualityInfo;->setType(I)V -Landroid/nfc/ApduList;-><init>()V -Landroid/nfc/ApduList;-><init>(Landroid/os/Parcel;)V -Landroid/nfc/ApduList;->add([B)V -Landroid/nfc/ApduList;->commands:Ljava/util/ArrayList; -Landroid/nfc/ApduList;->CREATOR:Landroid/os/Parcelable$Creator; -Landroid/nfc/ApduList;->get()Ljava/util/List; -Landroid/nfc/BeamShareData;-><init>(Landroid/nfc/NdefMessage;[Landroid/net/Uri;Landroid/os/UserHandle;I)V -Landroid/nfc/BeamShareData;->CREATOR:Landroid/os/Parcelable$Creator; -Landroid/nfc/BeamShareData;->flags:I -Landroid/nfc/BeamShareData;->ndefMessage:Landroid/nfc/NdefMessage; -Landroid/nfc/BeamShareData;->uris:[Landroid/net/Uri; -Landroid/nfc/BeamShareData;->userHandle:Landroid/os/UserHandle; -Landroid/nfc/cardemulation/AidGroup;-><init>(Ljava/util/List;Ljava/lang/String;)V -Landroid/nfc/cardemulation/AidGroup;->isValidCategory(Ljava/lang/String;)Z -Landroid/nfc/cardemulation/AidGroup;->MAX_NUM_AIDS:I -Landroid/nfc/cardemulation/AidGroup;->TAG:Ljava/lang/String; -Landroid/nfc/cardemulation/ApduServiceInfo;->dump(Ljava/io/FileDescriptor;Ljava/io/PrintWriter;[Ljava/lang/String;)V -Landroid/nfc/cardemulation/ApduServiceInfo;->getAidGroups()Ljava/util/ArrayList; -Landroid/nfc/cardemulation/ApduServiceInfo;->getAids()Ljava/util/List; -Landroid/nfc/cardemulation/ApduServiceInfo;->getCategoryForAid(Ljava/lang/String;)Ljava/lang/String; -Landroid/nfc/cardemulation/ApduServiceInfo;->getComponent()Landroid/content/ComponentName; -Landroid/nfc/cardemulation/ApduServiceInfo;->getDynamicAidGroupForCategory(Ljava/lang/String;)Landroid/nfc/cardemulation/AidGroup; -Landroid/nfc/cardemulation/ApduServiceInfo;->getPrefixAids()Ljava/util/List; -Landroid/nfc/cardemulation/ApduServiceInfo;->getSubsetAids()Ljava/util/List; -Landroid/nfc/cardemulation/ApduServiceInfo;->hasCategory(Ljava/lang/String;)Z -Landroid/nfc/cardemulation/ApduServiceInfo;->loadAppLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence; -Landroid/nfc/cardemulation/ApduServiceInfo;->loadIcon(Landroid/content/pm/PackageManager;)Landroid/graphics/drawable/Drawable; -Landroid/nfc/cardemulation/ApduServiceInfo;->loadLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence; -Landroid/nfc/cardemulation/ApduServiceInfo;->mBannerResourceId:I -Landroid/nfc/cardemulation/ApduServiceInfo;->mDescription:Ljava/lang/String; -Landroid/nfc/cardemulation/ApduServiceInfo;->mOnHost:Z -Landroid/nfc/cardemulation/ApduServiceInfo;->mRequiresDeviceUnlock:Z -Landroid/nfc/cardemulation/ApduServiceInfo;->mSettingsActivityName:Ljava/lang/String; -Landroid/nfc/cardemulation/ApduServiceInfo;->mUid:I -Landroid/nfc/cardemulation/ApduServiceInfo;->removeDynamicAidGroupForCategory(Ljava/lang/String;)Z -Landroid/nfc/cardemulation/ApduServiceInfo;->setOrReplaceDynamicAidGroup(Landroid/nfc/cardemulation/AidGroup;)V -Landroid/nfc/cardemulation/ApduServiceInfo;->TAG:Ljava/lang/String; -Landroid/nfc/cardemulation/CardEmulation;-><init>(Landroid/content/Context;Landroid/nfc/INfcCardEmulation;)V -Landroid/nfc/cardemulation/CardEmulation;->getServices(Ljava/lang/String;)Ljava/util/List; -Landroid/nfc/cardemulation/CardEmulation;->isValidAid(Ljava/lang/String;)Z -Landroid/nfc/cardemulation/CardEmulation;->mContext:Landroid/content/Context; -Landroid/nfc/cardemulation/CardEmulation;->recoverService()V -Landroid/nfc/cardemulation/CardEmulation;->sCardEmus:Ljava/util/HashMap; -Landroid/nfc/cardemulation/CardEmulation;->setDefaultForNextTap(Landroid/content/ComponentName;)Z -Landroid/nfc/cardemulation/CardEmulation;->setDefaultServiceForCategory(Landroid/content/ComponentName;Ljava/lang/String;)Z -Landroid/nfc/cardemulation/CardEmulation;->sIsInitialized:Z -Landroid/nfc/cardemulation/CardEmulation;->sService:Landroid/nfc/INfcCardEmulation; -Landroid/nfc/cardemulation/CardEmulation;->TAG:Ljava/lang/String; -Landroid/nfc/cardemulation/HostApduService;->KEY_DATA:Ljava/lang/String; -Landroid/nfc/cardemulation/HostApduService;->mMessenger:Landroid/os/Messenger; -Landroid/nfc/cardemulation/HostApduService;->mNfcService:Landroid/os/Messenger; -Landroid/nfc/cardemulation/HostApduService;->MSG_COMMAND_APDU:I -Landroid/nfc/cardemulation/HostApduService;->MSG_DEACTIVATED:I -Landroid/nfc/cardemulation/HostApduService;->MSG_RESPONSE_APDU:I -Landroid/nfc/cardemulation/HostApduService;->MSG_UNHANDLED:I -Landroid/nfc/cardemulation/HostApduService;->TAG:Ljava/lang/String; -Landroid/nfc/cardemulation/HostNfcFService;->KEY_DATA:Ljava/lang/String; -Landroid/nfc/cardemulation/HostNfcFService;->KEY_MESSENGER:Ljava/lang/String; -Landroid/nfc/cardemulation/HostNfcFService;->mMessenger:Landroid/os/Messenger; -Landroid/nfc/cardemulation/HostNfcFService;->mNfcService:Landroid/os/Messenger; -Landroid/nfc/cardemulation/HostNfcFService;->MSG_COMMAND_PACKET:I -Landroid/nfc/cardemulation/HostNfcFService;->MSG_DEACTIVATED:I -Landroid/nfc/cardemulation/HostNfcFService;->MSG_RESPONSE_PACKET:I -Landroid/nfc/cardemulation/HostNfcFService;->TAG:Ljava/lang/String; -Landroid/nfc/cardemulation/NfcFCardEmulation;-><init>(Landroid/content/Context;Landroid/nfc/INfcFCardEmulation;)V -Landroid/nfc/cardemulation/NfcFCardEmulation;->getMaxNumOfRegisterableSystemCodes()I -Landroid/nfc/cardemulation/NfcFCardEmulation;->getNfcFServices()Ljava/util/List; -Landroid/nfc/cardemulation/NfcFCardEmulation;->isValidNfcid2(Ljava/lang/String;)Z -Landroid/nfc/cardemulation/NfcFCardEmulation;->isValidSystemCode(Ljava/lang/String;)Z -Landroid/nfc/cardemulation/NfcFCardEmulation;->mContext:Landroid/content/Context; -Landroid/nfc/cardemulation/NfcFCardEmulation;->recoverService()V -Landroid/nfc/cardemulation/NfcFCardEmulation;->sCardEmus:Ljava/util/HashMap; -Landroid/nfc/cardemulation/NfcFCardEmulation;->sIsInitialized:Z -Landroid/nfc/cardemulation/NfcFCardEmulation;->sService:Landroid/nfc/INfcFCardEmulation; -Landroid/nfc/cardemulation/NfcFCardEmulation;->TAG:Ljava/lang/String; -Landroid/nfc/cardemulation/NfcFServiceInfo;-><init>(Landroid/content/pm/PackageManager;Landroid/content/pm/ResolveInfo;)V -Landroid/nfc/cardemulation/NfcFServiceInfo;-><init>(Landroid/content/pm/ResolveInfo;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)V -Landroid/nfc/cardemulation/NfcFServiceInfo;->CREATOR:Landroid/os/Parcelable$Creator; -Landroid/nfc/cardemulation/NfcFServiceInfo;->DEFAULT_T3T_PMM:Ljava/lang/String; -Landroid/nfc/cardemulation/NfcFServiceInfo;->dump(Ljava/io/FileDescriptor;Ljava/io/PrintWriter;[Ljava/lang/String;)V -Landroid/nfc/cardemulation/NfcFServiceInfo;->getComponent()Landroid/content/ComponentName; -Landroid/nfc/cardemulation/NfcFServiceInfo;->getDescription()Ljava/lang/String; -Landroid/nfc/cardemulation/NfcFServiceInfo;->getNfcid2()Ljava/lang/String; -Landroid/nfc/cardemulation/NfcFServiceInfo;->getSystemCode()Ljava/lang/String; -Landroid/nfc/cardemulation/NfcFServiceInfo;->getT3tPmm()Ljava/lang/String; -Landroid/nfc/cardemulation/NfcFServiceInfo;->getUid()I -Landroid/nfc/cardemulation/NfcFServiceInfo;->loadIcon(Landroid/content/pm/PackageManager;)Landroid/graphics/drawable/Drawable; -Landroid/nfc/cardemulation/NfcFServiceInfo;->loadLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence; -Landroid/nfc/cardemulation/NfcFServiceInfo;->mDescription:Ljava/lang/String; -Landroid/nfc/cardemulation/NfcFServiceInfo;->mDynamicNfcid2:Ljava/lang/String; -Landroid/nfc/cardemulation/NfcFServiceInfo;->mDynamicSystemCode:Ljava/lang/String; -Landroid/nfc/cardemulation/NfcFServiceInfo;->mNfcid2:Ljava/lang/String; -Landroid/nfc/cardemulation/NfcFServiceInfo;->mService:Landroid/content/pm/ResolveInfo; -Landroid/nfc/cardemulation/NfcFServiceInfo;->mSystemCode:Ljava/lang/String; -Landroid/nfc/cardemulation/NfcFServiceInfo;->mT3tPmm:Ljava/lang/String; -Landroid/nfc/cardemulation/NfcFServiceInfo;->mUid:I -Landroid/nfc/cardemulation/NfcFServiceInfo;->setOrReplaceDynamicNfcid2(Ljava/lang/String;)V -Landroid/nfc/cardemulation/NfcFServiceInfo;->setOrReplaceDynamicSystemCode(Ljava/lang/String;)V -Landroid/nfc/cardemulation/NfcFServiceInfo;->TAG:Ljava/lang/String; -Landroid/nfc/ErrorCodes;-><init>()V -Landroid/nfc/ErrorCodes;->asString(I)Ljava/lang/String; -Landroid/nfc/ErrorCodes;->ERROR_BUFFER_TO_SMALL:I -Landroid/nfc/ErrorCodes;->ERROR_BUSY:I -Landroid/nfc/ErrorCodes;->ERROR_CANCELLED:I -Landroid/nfc/ErrorCodes;->ERROR_CONNECT:I -Landroid/nfc/ErrorCodes;->ERROR_DISCONNECT:I -Landroid/nfc/ErrorCodes;->ERROR_INSUFFICIENT_RESOURCES:I -Landroid/nfc/ErrorCodes;->ERROR_INVALID_PARAM:I -Landroid/nfc/ErrorCodes;->ERROR_IO:I -Landroid/nfc/ErrorCodes;->ERROR_NFC_ON:I -Landroid/nfc/ErrorCodes;->ERROR_NOT_INITIALIZED:I -Landroid/nfc/ErrorCodes;->ERROR_NOT_SUPPORTED:I -Landroid/nfc/ErrorCodes;->ERROR_NO_SE_CONNECTED:I -Landroid/nfc/ErrorCodes;->ERROR_READ:I -Landroid/nfc/ErrorCodes;->ERROR_SAP_USED:I -Landroid/nfc/ErrorCodes;->ERROR_SERVICE_NAME_USED:I -Landroid/nfc/ErrorCodes;->ERROR_SE_ALREADY_SELECTED:I -Landroid/nfc/ErrorCodes;->ERROR_SE_CONNECTED:I -Landroid/nfc/ErrorCodes;->ERROR_SOCKET_CREATION:I -Landroid/nfc/ErrorCodes;->ERROR_SOCKET_NOT_CONNECTED:I -Landroid/nfc/ErrorCodes;->ERROR_SOCKET_OPTIONS:I -Landroid/nfc/ErrorCodes;->ERROR_TIMEOUT:I -Landroid/nfc/ErrorCodes;->ERROR_WRITE:I -Landroid/nfc/ErrorCodes;->SUCCESS:I -Landroid/nfc/IAppCallback$Stub$Proxy;-><init>(Landroid/os/IBinder;)V -Landroid/nfc/IAppCallback$Stub$Proxy;->createBeamShareData(B)Landroid/nfc/BeamShareData; -Landroid/nfc/IAppCallback$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String; -Landroid/nfc/IAppCallback$Stub$Proxy;->mRemote:Landroid/os/IBinder; -Landroid/nfc/IAppCallback$Stub$Proxy;->onNdefPushComplete(B)V -Landroid/nfc/IAppCallback$Stub$Proxy;->onTagDiscovered(Landroid/nfc/Tag;)V -Landroid/nfc/IAppCallback$Stub;-><init>()V -Landroid/nfc/IAppCallback$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/IAppCallback; -Landroid/nfc/IAppCallback$Stub;->DESCRIPTOR:Ljava/lang/String; -Landroid/nfc/IAppCallback$Stub;->TRANSACTION_createBeamShareData:I -Landroid/nfc/IAppCallback$Stub;->TRANSACTION_onNdefPushComplete:I -Landroid/nfc/IAppCallback$Stub;->TRANSACTION_onTagDiscovered:I -Landroid/nfc/IAppCallback;->createBeamShareData(B)Landroid/nfc/BeamShareData; -Landroid/nfc/IAppCallback;->onNdefPushComplete(B)V -Landroid/nfc/IAppCallback;->onTagDiscovered(Landroid/nfc/Tag;)V -Landroid/nfc/INfcAdapter$Stub$Proxy;-><init>(Landroid/os/IBinder;)V -Landroid/nfc/INfcAdapter$Stub$Proxy;->addNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;[I)V -Landroid/nfc/INfcAdapter$Stub$Proxy;->disable(Z)Z -Landroid/nfc/INfcAdapter$Stub$Proxy;->disableNdefPush()Z -Landroid/nfc/INfcAdapter$Stub$Proxy;->dispatch(Landroid/nfc/Tag;)V -Landroid/nfc/INfcAdapter$Stub$Proxy;->enable()Z -Landroid/nfc/INfcAdapter$Stub$Proxy;->enableNdefPush()Z -Landroid/nfc/INfcAdapter$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String; -Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcAdapterExtrasInterface(Ljava/lang/String;)Landroid/nfc/INfcAdapterExtras; -Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcCardEmulationInterface()Landroid/nfc/INfcCardEmulation; -Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcDtaInterface(Ljava/lang/String;)Landroid/nfc/INfcDta; -Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcFCardEmulationInterface()Landroid/nfc/INfcFCardEmulation; -Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcTagInterface()Landroid/nfc/INfcTag; -Landroid/nfc/INfcAdapter$Stub$Proxy;->getState()I -Landroid/nfc/INfcAdapter$Stub$Proxy;->ignore(IILandroid/nfc/ITagRemovedCallback;)Z -Landroid/nfc/INfcAdapter$Stub$Proxy;->invokeBeam()V -Landroid/nfc/INfcAdapter$Stub$Proxy;->invokeBeamInternal(Landroid/nfc/BeamShareData;)V -Landroid/nfc/INfcAdapter$Stub$Proxy;->isNdefPushEnabled()Z -Landroid/nfc/INfcAdapter$Stub$Proxy;->mRemote:Landroid/os/IBinder; -Landroid/nfc/INfcAdapter$Stub$Proxy;->pausePolling(I)V -Landroid/nfc/INfcAdapter$Stub$Proxy;->removeNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;)V -Landroid/nfc/INfcAdapter$Stub$Proxy;->resumePolling()V -Landroid/nfc/INfcAdapter$Stub$Proxy;->setAppCallback(Landroid/nfc/IAppCallback;)V -Landroid/nfc/INfcAdapter$Stub$Proxy;->setForegroundDispatch(Landroid/app/PendingIntent;[Landroid/content/IntentFilter;Landroid/nfc/TechListParcel;)V -Landroid/nfc/INfcAdapter$Stub$Proxy;->setP2pModes(II)V -Landroid/nfc/INfcAdapter$Stub$Proxy;->setReaderMode(Landroid/os/IBinder;Landroid/nfc/IAppCallback;ILandroid/os/Bundle;)V -Landroid/nfc/INfcAdapter$Stub$Proxy;->verifyNfcPermission()V -Landroid/nfc/INfcAdapter$Stub;-><init>()V -Landroid/nfc/INfcAdapter$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcAdapter; -Landroid/nfc/INfcAdapter$Stub;->DESCRIPTOR:Ljava/lang/String; -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_addNfcUnlockHandler:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_disable:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_disableNdefPush:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_dispatch:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_enableNdefPush:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcAdapterExtrasInterface:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcCardEmulationInterface:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcDtaInterface:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcFCardEmulationInterface:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcTagInterface:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getState:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_ignore:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_invokeBeam:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_invokeBeamInternal:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_isNdefPushEnabled:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_pausePolling:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_removeNfcUnlockHandler:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_resumePolling:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setAppCallback:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setForegroundDispatch:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setP2pModes:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setReaderMode:I -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_verifyNfcPermission:I -Landroid/nfc/INfcAdapter;->addNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;[I)V -Landroid/nfc/INfcAdapter;->disable(Z)Z -Landroid/nfc/INfcAdapter;->disableNdefPush()Z -Landroid/nfc/INfcAdapter;->dispatch(Landroid/nfc/Tag;)V -Landroid/nfc/INfcAdapter;->enable()Z -Landroid/nfc/INfcAdapter;->enableNdefPush()Z -Landroid/nfc/INfcAdapter;->getNfcAdapterExtrasInterface(Ljava/lang/String;)Landroid/nfc/INfcAdapterExtras; -Landroid/nfc/INfcAdapter;->getNfcCardEmulationInterface()Landroid/nfc/INfcCardEmulation; -Landroid/nfc/INfcAdapter;->getNfcDtaInterface(Ljava/lang/String;)Landroid/nfc/INfcDta; -Landroid/nfc/INfcAdapter;->getNfcFCardEmulationInterface()Landroid/nfc/INfcFCardEmulation; -Landroid/nfc/INfcAdapter;->getNfcTagInterface()Landroid/nfc/INfcTag; -Landroid/nfc/INfcAdapter;->getState()I -Landroid/nfc/INfcAdapter;->ignore(IILandroid/nfc/ITagRemovedCallback;)Z -Landroid/nfc/INfcAdapter;->invokeBeam()V -Landroid/nfc/INfcAdapter;->invokeBeamInternal(Landroid/nfc/BeamShareData;)V -Landroid/nfc/INfcAdapter;->isNdefPushEnabled()Z -Landroid/nfc/INfcAdapter;->pausePolling(I)V -Landroid/nfc/INfcAdapter;->removeNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;)V -Landroid/nfc/INfcAdapter;->resumePolling()V -Landroid/nfc/INfcAdapter;->setAppCallback(Landroid/nfc/IAppCallback;)V -Landroid/nfc/INfcAdapter;->setForegroundDispatch(Landroid/app/PendingIntent;[Landroid/content/IntentFilter;Landroid/nfc/TechListParcel;)V -Landroid/nfc/INfcAdapter;->setP2pModes(II)V -Landroid/nfc/INfcAdapter;->setReaderMode(Landroid/os/IBinder;Landroid/nfc/IAppCallback;ILandroid/os/Bundle;)V -Landroid/nfc/INfcAdapter;->verifyNfcPermission()V -Landroid/nfc/INfcAdapterExtras$Stub$Proxy;-><init>(Landroid/os/IBinder;)V -Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->authenticate(Ljava/lang/String;[B)V -Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->close(Ljava/lang/String;Landroid/os/IBinder;)Landroid/os/Bundle; -Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->getCardEmulationRoute(Ljava/lang/String;)I -Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->getDriverName(Ljava/lang/String;)Ljava/lang/String; -Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String; -Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->mRemote:Landroid/os/IBinder; -Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->open(Ljava/lang/String;Landroid/os/IBinder;)Landroid/os/Bundle; -Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->setCardEmulationRoute(Ljava/lang/String;I)V -Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->transceive(Ljava/lang/String;[B)Landroid/os/Bundle; -Landroid/nfc/INfcAdapterExtras$Stub;-><init>()V -Landroid/nfc/INfcAdapterExtras$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcAdapterExtras; -Landroid/nfc/INfcAdapterExtras$Stub;->DESCRIPTOR:Ljava/lang/String; -Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_authenticate:I -Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_close:I -Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_getCardEmulationRoute:I -Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_getDriverName:I -Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_open:I -Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_setCardEmulationRoute:I -Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_transceive:I -Landroid/nfc/INfcCardEmulation$Stub$Proxy;-><init>(Landroid/os/IBinder;)V -Landroid/nfc/INfcCardEmulation$Stub$Proxy;->getAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Landroid/nfc/cardemulation/AidGroup; -Landroid/nfc/INfcCardEmulation$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String; -Landroid/nfc/INfcCardEmulation$Stub$Proxy;->getServices(ILjava/lang/String;)Ljava/util/List; -Landroid/nfc/INfcCardEmulation$Stub$Proxy;->isDefaultServiceForAid(ILandroid/content/ComponentName;Ljava/lang/String;)Z -Landroid/nfc/INfcCardEmulation$Stub$Proxy;->isDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z -Landroid/nfc/INfcCardEmulation$Stub$Proxy;->mRemote:Landroid/os/IBinder; -Landroid/nfc/INfcCardEmulation$Stub$Proxy;->registerAidGroupForService(ILandroid/content/ComponentName;Landroid/nfc/cardemulation/AidGroup;)Z -Landroid/nfc/INfcCardEmulation$Stub$Proxy;->removeAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z -Landroid/nfc/INfcCardEmulation$Stub$Proxy;->setDefaultForNextTap(ILandroid/content/ComponentName;)Z -Landroid/nfc/INfcCardEmulation$Stub$Proxy;->setDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z -Landroid/nfc/INfcCardEmulation$Stub$Proxy;->setPreferredService(Landroid/content/ComponentName;)Z -Landroid/nfc/INfcCardEmulation$Stub$Proxy;->supportsAidPrefixRegistration()Z -Landroid/nfc/INfcCardEmulation$Stub$Proxy;->unsetPreferredService()Z -Landroid/nfc/INfcCardEmulation$Stub;-><init>()V -Landroid/nfc/INfcCardEmulation$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcCardEmulation; -Landroid/nfc/INfcCardEmulation$Stub;->DESCRIPTOR:Ljava/lang/String; -Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_getAidGroupForService:I -Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_getServices:I -Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_isDefaultServiceForAid:I -Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_isDefaultServiceForCategory:I -Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_registerAidGroupForService:I -Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_removeAidGroupForService:I -Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_setDefaultForNextTap:I -Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_setDefaultServiceForCategory:I -Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_setPreferredService:I -Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_supportsAidPrefixRegistration:I -Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_unsetPreferredService:I -Landroid/nfc/INfcCardEmulation;->getAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Landroid/nfc/cardemulation/AidGroup; -Landroid/nfc/INfcCardEmulation;->getServices(ILjava/lang/String;)Ljava/util/List; -Landroid/nfc/INfcCardEmulation;->isDefaultServiceForAid(ILandroid/content/ComponentName;Ljava/lang/String;)Z -Landroid/nfc/INfcCardEmulation;->isDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z -Landroid/nfc/INfcCardEmulation;->registerAidGroupForService(ILandroid/content/ComponentName;Landroid/nfc/cardemulation/AidGroup;)Z -Landroid/nfc/INfcCardEmulation;->removeAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z -Landroid/nfc/INfcCardEmulation;->setDefaultForNextTap(ILandroid/content/ComponentName;)Z -Landroid/nfc/INfcCardEmulation;->setDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z -Landroid/nfc/INfcCardEmulation;->setPreferredService(Landroid/content/ComponentName;)Z -Landroid/nfc/INfcCardEmulation;->supportsAidPrefixRegistration()Z -Landroid/nfc/INfcCardEmulation;->unsetPreferredService()Z -Landroid/nfc/INfcDta$Stub$Proxy;-><init>(Landroid/os/IBinder;)V -Landroid/nfc/INfcDta$Stub$Proxy;->disableClient()V -Landroid/nfc/INfcDta$Stub$Proxy;->disableDta()V -Landroid/nfc/INfcDta$Stub$Proxy;->disableServer()V -Landroid/nfc/INfcDta$Stub$Proxy;->enableClient(Ljava/lang/String;III)Z -Landroid/nfc/INfcDta$Stub$Proxy;->enableDta()V -Landroid/nfc/INfcDta$Stub$Proxy;->enableServer(Ljava/lang/String;IIII)Z -Landroid/nfc/INfcDta$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String; -Landroid/nfc/INfcDta$Stub$Proxy;->mRemote:Landroid/os/IBinder; -Landroid/nfc/INfcDta$Stub$Proxy;->registerMessageService(Ljava/lang/String;)Z -Landroid/nfc/INfcDta$Stub;-><init>()V -Landroid/nfc/INfcDta$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcDta; -Landroid/nfc/INfcDta$Stub;->DESCRIPTOR:Ljava/lang/String; -Landroid/nfc/INfcDta$Stub;->TRANSACTION_disableClient:I -Landroid/nfc/INfcDta$Stub;->TRANSACTION_disableDta:I -Landroid/nfc/INfcDta$Stub;->TRANSACTION_disableServer:I -Landroid/nfc/INfcDta$Stub;->TRANSACTION_enableClient:I -Landroid/nfc/INfcDta$Stub;->TRANSACTION_enableDta:I -Landroid/nfc/INfcDta$Stub;->TRANSACTION_enableServer:I -Landroid/nfc/INfcDta$Stub;->TRANSACTION_registerMessageService:I -Landroid/nfc/INfcDta;->disableClient()V -Landroid/nfc/INfcDta;->disableDta()V -Landroid/nfc/INfcDta;->disableServer()V -Landroid/nfc/INfcDta;->enableClient(Ljava/lang/String;III)Z -Landroid/nfc/INfcDta;->enableDta()V -Landroid/nfc/INfcDta;->enableServer(Ljava/lang/String;IIII)Z -Landroid/nfc/INfcDta;->registerMessageService(Ljava/lang/String;)Z -Landroid/nfc/INfcFCardEmulation$Stub$Proxy;-><init>(Landroid/os/IBinder;)V -Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->disableNfcFForegroundService()Z -Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->enableNfcFForegroundService(Landroid/content/ComponentName;)Z -Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String; -Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getMaxNumOfRegisterableSystemCodes()I -Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getNfcFServices(I)Ljava/util/List; -Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getNfcid2ForService(ILandroid/content/ComponentName;)Ljava/lang/String; -Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getSystemCodeForService(ILandroid/content/ComponentName;)Ljava/lang/String; -Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->mRemote:Landroid/os/IBinder; -Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->registerSystemCodeForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z -Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->removeSystemCodeForService(ILandroid/content/ComponentName;)Z -Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->setNfcid2ForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z -Landroid/nfc/INfcFCardEmulation$Stub;-><init>()V -Landroid/nfc/INfcFCardEmulation$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcFCardEmulation; -Landroid/nfc/INfcFCardEmulation$Stub;->DESCRIPTOR:Ljava/lang/String; -Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_disableNfcFForegroundService:I -Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_enableNfcFForegroundService:I -Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getMaxNumOfRegisterableSystemCodes:I -Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getNfcFServices:I -Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getNfcid2ForService:I -Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getSystemCodeForService:I -Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_registerSystemCodeForService:I -Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_removeSystemCodeForService:I -Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_setNfcid2ForService:I -Landroid/nfc/INfcFCardEmulation;->disableNfcFForegroundService()Z -Landroid/nfc/INfcFCardEmulation;->enableNfcFForegroundService(Landroid/content/ComponentName;)Z -Landroid/nfc/INfcFCardEmulation;->getMaxNumOfRegisterableSystemCodes()I -Landroid/nfc/INfcFCardEmulation;->getNfcFServices(I)Ljava/util/List; -Landroid/nfc/INfcFCardEmulation;->getNfcid2ForService(ILandroid/content/ComponentName;)Ljava/lang/String; -Landroid/nfc/INfcFCardEmulation;->getSystemCodeForService(ILandroid/content/ComponentName;)Ljava/lang/String; -Landroid/nfc/INfcFCardEmulation;->registerSystemCodeForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z -Landroid/nfc/INfcFCardEmulation;->removeSystemCodeForService(ILandroid/content/ComponentName;)Z -Landroid/nfc/INfcFCardEmulation;->setNfcid2ForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z -Landroid/nfc/INfcTag$Stub$Proxy;-><init>(Landroid/os/IBinder;)V -Landroid/nfc/INfcTag$Stub$Proxy;->canMakeReadOnly(I)Z -Landroid/nfc/INfcTag$Stub$Proxy;->connect(II)I -Landroid/nfc/INfcTag$Stub$Proxy;->formatNdef(I[B)I -Landroid/nfc/INfcTag$Stub$Proxy;->getExtendedLengthApdusSupported()Z -Landroid/nfc/INfcTag$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String; -Landroid/nfc/INfcTag$Stub$Proxy;->getMaxTransceiveLength(I)I -Landroid/nfc/INfcTag$Stub$Proxy;->getTechList(I)[I -Landroid/nfc/INfcTag$Stub$Proxy;->getTimeout(I)I -Landroid/nfc/INfcTag$Stub$Proxy;->isNdef(I)Z -Landroid/nfc/INfcTag$Stub$Proxy;->isPresent(I)Z -Landroid/nfc/INfcTag$Stub$Proxy;->mRemote:Landroid/os/IBinder; -Landroid/nfc/INfcTag$Stub$Proxy;->ndefIsWritable(I)Z -Landroid/nfc/INfcTag$Stub$Proxy;->ndefMakeReadOnly(I)I -Landroid/nfc/INfcTag$Stub$Proxy;->ndefRead(I)Landroid/nfc/NdefMessage; -Landroid/nfc/INfcTag$Stub$Proxy;->ndefWrite(ILandroid/nfc/NdefMessage;)I -Landroid/nfc/INfcTag$Stub$Proxy;->reconnect(I)I -Landroid/nfc/INfcTag$Stub$Proxy;->rediscover(I)Landroid/nfc/Tag; -Landroid/nfc/INfcTag$Stub$Proxy;->resetTimeouts()V -Landroid/nfc/INfcTag$Stub$Proxy;->setTimeout(II)I -Landroid/nfc/INfcTag$Stub$Proxy;->transceive(I[BZ)Landroid/nfc/TransceiveResult; -Landroid/nfc/INfcTag$Stub;-><init>()V -Landroid/nfc/INfcTag$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcTag; -Landroid/nfc/INfcTag$Stub;->DESCRIPTOR:Ljava/lang/String; -Landroid/nfc/INfcTag$Stub;->TRANSACTION_canMakeReadOnly:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_connect:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_formatNdef:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_getExtendedLengthApdusSupported:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_getMaxTransceiveLength:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_getTechList:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_getTimeout:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_isNdef:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_isPresent:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefIsWritable:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefMakeReadOnly:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefRead:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefWrite:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_reconnect:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_rediscover:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_resetTimeouts:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_setTimeout:I -Landroid/nfc/INfcTag$Stub;->TRANSACTION_transceive:I -Landroid/nfc/INfcTag;->canMakeReadOnly(I)Z -Landroid/nfc/INfcTag;->connect(II)I -Landroid/nfc/INfcTag;->formatNdef(I[B)I -Landroid/nfc/INfcTag;->getExtendedLengthApdusSupported()Z -Landroid/nfc/INfcTag;->getMaxTransceiveLength(I)I -Landroid/nfc/INfcTag;->getTechList(I)[I -Landroid/nfc/INfcTag;->getTimeout(I)I -Landroid/nfc/INfcTag;->isNdef(I)Z -Landroid/nfc/INfcTag;->isPresent(I)Z -Landroid/nfc/INfcTag;->ndefIsWritable(I)Z -Landroid/nfc/INfcTag;->ndefMakeReadOnly(I)I -Landroid/nfc/INfcTag;->ndefRead(I)Landroid/nfc/NdefMessage; -Landroid/nfc/INfcTag;->ndefWrite(ILandroid/nfc/NdefMessage;)I -Landroid/nfc/INfcTag;->reconnect(I)I -Landroid/nfc/INfcTag;->rediscover(I)Landroid/nfc/Tag; -Landroid/nfc/INfcTag;->resetTimeouts()V -Landroid/nfc/INfcTag;->setTimeout(II)I -Landroid/nfc/INfcTag;->transceive(I[BZ)Landroid/nfc/TransceiveResult; -Landroid/nfc/INfcUnlockHandler$Stub$Proxy;-><init>(Landroid/os/IBinder;)V -Landroid/nfc/INfcUnlockHandler$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String; -Landroid/nfc/INfcUnlockHandler$Stub$Proxy;->mRemote:Landroid/os/IBinder; -Landroid/nfc/INfcUnlockHandler$Stub$Proxy;->onUnlockAttempted(Landroid/nfc/Tag;)Z -Landroid/nfc/INfcUnlockHandler$Stub;-><init>()V -Landroid/nfc/INfcUnlockHandler$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcUnlockHandler; -Landroid/nfc/INfcUnlockHandler$Stub;->DESCRIPTOR:Ljava/lang/String; -Landroid/nfc/INfcUnlockHandler$Stub;->TRANSACTION_onUnlockAttempted:I -Landroid/nfc/INfcUnlockHandler;->onUnlockAttempted(Landroid/nfc/Tag;)Z -Landroid/nfc/ITagRemovedCallback$Stub$Proxy;-><init>(Landroid/os/IBinder;)V -Landroid/nfc/ITagRemovedCallback$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String; -Landroid/nfc/ITagRemovedCallback$Stub$Proxy;->mRemote:Landroid/os/IBinder; -Landroid/nfc/ITagRemovedCallback$Stub$Proxy;->onTagRemoved()V -Landroid/nfc/ITagRemovedCallback$Stub;-><init>()V -Landroid/nfc/ITagRemovedCallback$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/ITagRemovedCallback; -Landroid/nfc/ITagRemovedCallback$Stub;->DESCRIPTOR:Ljava/lang/String; -Landroid/nfc/ITagRemovedCallback$Stub;->TRANSACTION_onTagRemoved:I -Landroid/nfc/ITagRemovedCallback;->onTagRemoved()V -Landroid/nfc/NdefMessage;->mRecords:[Landroid/nfc/NdefRecord; -Landroid/nfc/NdefRecord;->bytesToString([B)Ljava/lang/StringBuilder; -Landroid/nfc/NdefRecord;->EMPTY_BYTE_ARRAY:[B -Landroid/nfc/NdefRecord;->ensureSanePayloadSize(J)V -Landroid/nfc/NdefRecord;->FLAG_CF:B -Landroid/nfc/NdefRecord;->FLAG_IL:B -Landroid/nfc/NdefRecord;->FLAG_MB:B -Landroid/nfc/NdefRecord;->FLAG_ME:B -Landroid/nfc/NdefRecord;->FLAG_SR:B -Landroid/nfc/NdefRecord;->getByteLength()I -Landroid/nfc/NdefRecord;->MAX_PAYLOAD_SIZE:I -Landroid/nfc/NdefRecord;->mPayload:[B -Landroid/nfc/NdefRecord;->mTnf:S -Landroid/nfc/NdefRecord;->mType:[B -Landroid/nfc/NdefRecord;->parse(Ljava/nio/ByteBuffer;Z)[Landroid/nfc/NdefRecord; -Landroid/nfc/NdefRecord;->parseWktUri()Landroid/net/Uri; -Landroid/nfc/NdefRecord;->RTD_ANDROID_APP:[B -Landroid/nfc/NdefRecord;->TNF_RESERVED:S -Landroid/nfc/NdefRecord;->toUri(Z)Landroid/net/Uri; -Landroid/nfc/NdefRecord;->URI_PREFIX_MAP:[Ljava/lang/String; -Landroid/nfc/NdefRecord;->validateTnf(S[B[B[B)Ljava/lang/String; -Landroid/nfc/NdefRecord;->writeToByteBuffer(Ljava/nio/ByteBuffer;ZZ)V -Landroid/nfc/NfcActivityManager$NfcActivityState;->activity:Landroid/app/Activity; -Landroid/nfc/NfcActivityManager$NfcActivityState;->destroy()V -Landroid/nfc/NfcActivityManager$NfcActivityState;->flags:I -Landroid/nfc/NfcActivityManager$NfcActivityState;->ndefMessage:Landroid/nfc/NdefMessage; -Landroid/nfc/NfcActivityManager$NfcActivityState;->ndefMessageCallback:Landroid/nfc/NfcAdapter$CreateNdefMessageCallback; -Landroid/nfc/NfcActivityManager$NfcActivityState;->onNdefPushCompleteCallback:Landroid/nfc/NfcAdapter$OnNdefPushCompleteCallback; -Landroid/nfc/NfcActivityManager$NfcActivityState;->readerCallback:Landroid/nfc/NfcAdapter$ReaderCallback; -Landroid/nfc/NfcActivityManager$NfcActivityState;->readerModeExtras:Landroid/os/Bundle; -Landroid/nfc/NfcActivityManager$NfcActivityState;->readerModeFlags:I -Landroid/nfc/NfcActivityManager$NfcActivityState;->resumed:Z -Landroid/nfc/NfcActivityManager$NfcActivityState;->token:Landroid/os/Binder; -Landroid/nfc/NfcActivityManager$NfcActivityState;->uriCallback:Landroid/nfc/NfcAdapter$CreateBeamUrisCallback; -Landroid/nfc/NfcActivityManager$NfcActivityState;->uris:[Landroid/net/Uri; -Landroid/nfc/NfcActivityManager$NfcApplicationState;->app:Landroid/app/Application; -Landroid/nfc/NfcActivityManager$NfcApplicationState;->refCount:I -Landroid/nfc/NfcActivityManager$NfcApplicationState;->register()V -Landroid/nfc/NfcActivityManager$NfcApplicationState;->unregister()V -Landroid/nfc/NfcActivityManager;-><init>(Landroid/nfc/NfcAdapter;)V -Landroid/nfc/NfcActivityManager;->createBeamShareData(B)Landroid/nfc/BeamShareData; -Landroid/nfc/NfcActivityManager;->DBG:Ljava/lang/Boolean; -Landroid/nfc/NfcActivityManager;->destroyActivityState(Landroid/app/Activity;)V -Landroid/nfc/NfcActivityManager;->disableReaderMode(Landroid/app/Activity;)V -Landroid/nfc/NfcActivityManager;->enableReaderMode(Landroid/app/Activity;Landroid/nfc/NfcAdapter$ReaderCallback;ILandroid/os/Bundle;)V -Landroid/nfc/NfcActivityManager;->findActivityState(Landroid/app/Activity;)Landroid/nfc/NfcActivityManager$NfcActivityState; -Landroid/nfc/NfcActivityManager;->findAppState(Landroid/app/Application;)Landroid/nfc/NfcActivityManager$NfcApplicationState; -Landroid/nfc/NfcActivityManager;->findResumedActivityState()Landroid/nfc/NfcActivityManager$NfcActivityState; -Landroid/nfc/NfcActivityManager;->getActivityState(Landroid/app/Activity;)Landroid/nfc/NfcActivityManager$NfcActivityState; -Landroid/nfc/NfcActivityManager;->mActivities:Ljava/util/List; -Landroid/nfc/NfcActivityManager;->mApps:Ljava/util/List; -Landroid/nfc/NfcActivityManager;->onNdefPushComplete(B)V -Landroid/nfc/NfcActivityManager;->onTagDiscovered(Landroid/nfc/Tag;)V -Landroid/nfc/NfcActivityManager;->registerApplication(Landroid/app/Application;)V -Landroid/nfc/NfcActivityManager;->requestNfcServiceCallback()V -Landroid/nfc/NfcActivityManager;->setNdefPushContentUri(Landroid/app/Activity;[Landroid/net/Uri;)V -Landroid/nfc/NfcActivityManager;->setNdefPushContentUriCallback(Landroid/app/Activity;Landroid/nfc/NfcAdapter$CreateBeamUrisCallback;)V -Landroid/nfc/NfcActivityManager;->setNdefPushMessage(Landroid/app/Activity;Landroid/nfc/NdefMessage;I)V -Landroid/nfc/NfcActivityManager;->setNdefPushMessageCallback(Landroid/app/Activity;Landroid/nfc/NfcAdapter$CreateNdefMessageCallback;I)V -Landroid/nfc/NfcActivityManager;->setOnNdefPushCompleteCallback(Landroid/app/Activity;Landroid/nfc/NfcAdapter$OnNdefPushCompleteCallback;)V -Landroid/nfc/NfcActivityManager;->setReaderMode(Landroid/os/Binder;ILandroid/os/Bundle;)V -Landroid/nfc/NfcActivityManager;->TAG:Ljava/lang/String; -Landroid/nfc/NfcActivityManager;->unregisterApplication(Landroid/app/Application;)V -Landroid/nfc/NfcActivityManager;->verifyNfcPermission()V -Landroid/nfc/NfcAdapter;-><init>(Landroid/content/Context;)V -Landroid/nfc/NfcAdapter;->ACTION_HANDOVER_TRANSFER_DONE:Ljava/lang/String; -Landroid/nfc/NfcAdapter;->ACTION_HANDOVER_TRANSFER_STARTED:Ljava/lang/String; -Landroid/nfc/NfcAdapter;->ACTION_TAG_LEFT_FIELD:Ljava/lang/String; -Landroid/nfc/NfcAdapter;->disableForegroundDispatchInternal(Landroid/app/Activity;Z)V -Landroid/nfc/NfcAdapter;->dispatch(Landroid/nfc/Tag;)V -Landroid/nfc/NfcAdapter;->enforceResumed(Landroid/app/Activity;)V -Landroid/nfc/NfcAdapter;->EXTRA_HANDOVER_TRANSFER_STATUS:Ljava/lang/String; -Landroid/nfc/NfcAdapter;->EXTRA_HANDOVER_TRANSFER_URI:Ljava/lang/String; -Landroid/nfc/NfcAdapter;->getCardEmulationService()Landroid/nfc/INfcCardEmulation; -Landroid/nfc/NfcAdapter;->getNfcDtaInterface()Landroid/nfc/INfcDta; -Landroid/nfc/NfcAdapter;->getNfcFCardEmulationService()Landroid/nfc/INfcFCardEmulation; -Landroid/nfc/NfcAdapter;->getSdkVersion()I -Landroid/nfc/NfcAdapter;->getServiceInterface()Landroid/nfc/INfcAdapter; -Landroid/nfc/NfcAdapter;->getTagService()Landroid/nfc/INfcTag; -Landroid/nfc/NfcAdapter;->HANDOVER_TRANSFER_STATUS_FAILURE:I -Landroid/nfc/NfcAdapter;->HANDOVER_TRANSFER_STATUS_SUCCESS:I -Landroid/nfc/NfcAdapter;->hasNfcFeature()Z -Landroid/nfc/NfcAdapter;->hasNfcHceFeature()Z -Landroid/nfc/NfcAdapter;->invokeBeam(Landroid/nfc/BeamShareData;)Z -Landroid/nfc/NfcAdapter;->mContext:Landroid/content/Context; -Landroid/nfc/NfcAdapter;->mForegroundDispatchListener:Landroid/app/OnActivityPausedListener; -Landroid/nfc/NfcAdapter;->mLock:Ljava/lang/Object; -Landroid/nfc/NfcAdapter;->mNfcActivityManager:Landroid/nfc/NfcActivityManager; -Landroid/nfc/NfcAdapter;->mNfcUnlockHandlers:Ljava/util/HashMap; -Landroid/nfc/NfcAdapter;->mTagRemovedListener:Landroid/nfc/ITagRemovedCallback; -Landroid/nfc/NfcAdapter;->pausePolling(I)V -Landroid/nfc/NfcAdapter;->resumePolling()V -Landroid/nfc/NfcAdapter;->sCardEmulationService:Landroid/nfc/INfcCardEmulation; -Landroid/nfc/NfcAdapter;->setP2pModes(II)V -Landroid/nfc/NfcAdapter;->sHasNfcFeature:Z -Landroid/nfc/NfcAdapter;->sIsInitialized:Z -Landroid/nfc/NfcAdapter;->sNfcAdapters:Ljava/util/HashMap; -Landroid/nfc/NfcAdapter;->sNfcFCardEmulationService:Landroid/nfc/INfcFCardEmulation; -Landroid/nfc/NfcAdapter;->sNullContextNfcAdapter:Landroid/nfc/NfcAdapter; -Landroid/nfc/NfcAdapter;->sTagService:Landroid/nfc/INfcTag; -Landroid/nfc/NfcAdapter;->TAG:Ljava/lang/String; -Landroid/nfc/NfcEvent;-><init>(Landroid/nfc/NfcAdapter;B)V -Landroid/nfc/NfcManager;->mAdapter:Landroid/nfc/NfcAdapter; -Landroid/nfc/Tag;-><init>([B[I[Landroid/os/Bundle;ILandroid/nfc/INfcTag;)V -Landroid/nfc/Tag;->createMockTag([B[I[Landroid/os/Bundle;)Landroid/nfc/Tag; -Landroid/nfc/Tag;->generateTechStringList([I)[Ljava/lang/String; -Landroid/nfc/Tag;->getConnectedTechnology()I -Landroid/nfc/Tag;->getTechCodeList()[I -Landroid/nfc/Tag;->getTechCodesFromStrings([Ljava/lang/String;)[I -Landroid/nfc/Tag;->getTechExtras(I)Landroid/os/Bundle; -Landroid/nfc/Tag;->getTechStringToCodeMap()Ljava/util/HashMap; -Landroid/nfc/Tag;->hasTech(I)Z -Landroid/nfc/Tag;->mConnectedTechnology:I -Landroid/nfc/Tag;->mServiceHandle:I -Landroid/nfc/Tag;->mTagService:Landroid/nfc/INfcTag; -Landroid/nfc/Tag;->mTechExtras:[Landroid/os/Bundle; -Landroid/nfc/Tag;->mTechList:[I -Landroid/nfc/Tag;->mTechStringList:[Ljava/lang/String; -Landroid/nfc/Tag;->readBytesWithNull(Landroid/os/Parcel;)[B -Landroid/nfc/Tag;->rediscover()Landroid/nfc/Tag; -Landroid/nfc/Tag;->setConnectedTechnology(I)V -Landroid/nfc/Tag;->setTechnologyDisconnected()V -Landroid/nfc/Tag;->writeBytesWithNull(Landroid/os/Parcel;[B)V -Landroid/nfc/tech/BasicTagTechnology;-><init>(Landroid/nfc/Tag;I)V -Landroid/nfc/tech/BasicTagTechnology;->checkConnected()V -Landroid/nfc/tech/BasicTagTechnology;->getMaxTransceiveLengthInternal()I -Landroid/nfc/tech/BasicTagTechnology;->mIsConnected:Z -Landroid/nfc/tech/BasicTagTechnology;->mSelectedTechnology:I -Landroid/nfc/tech/BasicTagTechnology;->mTag:Landroid/nfc/Tag; -Landroid/nfc/tech/BasicTagTechnology;->reconnect()V -Landroid/nfc/tech/BasicTagTechnology;->TAG:Ljava/lang/String; -Landroid/nfc/tech/BasicTagTechnology;->transceive([BZ)[B -Landroid/nfc/tech/IsoDep;-><init>(Landroid/nfc/Tag;)V -Landroid/nfc/tech/IsoDep;->EXTRA_HIST_BYTES:Ljava/lang/String; -Landroid/nfc/tech/IsoDep;->EXTRA_HI_LAYER_RESP:Ljava/lang/String; -Landroid/nfc/tech/IsoDep;->mHiLayerResponse:[B -Landroid/nfc/tech/IsoDep;->mHistBytes:[B -Landroid/nfc/tech/IsoDep;->TAG:Ljava/lang/String; -Landroid/nfc/tech/MifareClassic;-><init>(Landroid/nfc/Tag;)V -Landroid/nfc/tech/MifareClassic;->authenticate(I[BZ)Z -Landroid/nfc/tech/MifareClassic;->isEmulated()Z -Landroid/nfc/tech/MifareClassic;->MAX_BLOCK_COUNT:I -Landroid/nfc/tech/MifareClassic;->MAX_SECTOR_COUNT:I -Landroid/nfc/tech/MifareClassic;->mIsEmulated:Z -Landroid/nfc/tech/MifareClassic;->mSize:I -Landroid/nfc/tech/MifareClassic;->mType:I -Landroid/nfc/tech/MifareClassic;->TAG:Ljava/lang/String; -Landroid/nfc/tech/MifareClassic;->validateBlock(I)V -Landroid/nfc/tech/MifareClassic;->validateSector(I)V -Landroid/nfc/tech/MifareClassic;->validateValueOperand(I)V -Landroid/nfc/tech/MifareUltralight;-><init>(Landroid/nfc/Tag;)V -Landroid/nfc/tech/MifareUltralight;->EXTRA_IS_UL_C:Ljava/lang/String; -Landroid/nfc/tech/MifareUltralight;->MAX_PAGE_COUNT:I -Landroid/nfc/tech/MifareUltralight;->mType:I -Landroid/nfc/tech/MifareUltralight;->NXP_MANUFACTURER_ID:I -Landroid/nfc/tech/MifareUltralight;->TAG:Ljava/lang/String; -Landroid/nfc/tech/MifareUltralight;->validatePageIndex(I)V -Landroid/nfc/tech/Ndef;-><init>(Landroid/nfc/Tag;)V -Landroid/nfc/tech/Ndef;->EXTRA_NDEF_CARDSTATE:Ljava/lang/String; -Landroid/nfc/tech/Ndef;->EXTRA_NDEF_MAXLENGTH:Ljava/lang/String; -Landroid/nfc/tech/Ndef;->EXTRA_NDEF_MSG:Ljava/lang/String; -Landroid/nfc/tech/Ndef;->EXTRA_NDEF_TYPE:Ljava/lang/String; -Landroid/nfc/tech/Ndef;->ICODE_SLI:Ljava/lang/String; -Landroid/nfc/tech/Ndef;->mCardState:I -Landroid/nfc/tech/Ndef;->mMaxNdefSize:I -Landroid/nfc/tech/Ndef;->mNdefMsg:Landroid/nfc/NdefMessage; -Landroid/nfc/tech/Ndef;->mNdefType:I -Landroid/nfc/tech/Ndef;->NDEF_MODE_READ_ONLY:I -Landroid/nfc/tech/Ndef;->NDEF_MODE_READ_WRITE:I -Landroid/nfc/tech/Ndef;->NDEF_MODE_UNKNOWN:I -Landroid/nfc/tech/Ndef;->TAG:Ljava/lang/String; -Landroid/nfc/tech/Ndef;->TYPE_1:I -Landroid/nfc/tech/Ndef;->TYPE_2:I -Landroid/nfc/tech/Ndef;->TYPE_3:I -Landroid/nfc/tech/Ndef;->TYPE_4:I -Landroid/nfc/tech/Ndef;->TYPE_ICODE_SLI:I -Landroid/nfc/tech/Ndef;->TYPE_MIFARE_CLASSIC:I -Landroid/nfc/tech/Ndef;->TYPE_OTHER:I -Landroid/nfc/tech/Ndef;->UNKNOWN:Ljava/lang/String; -Landroid/nfc/tech/NdefFormatable;-><init>(Landroid/nfc/Tag;)V -Landroid/nfc/tech/NdefFormatable;->format(Landroid/nfc/NdefMessage;Z)V -Landroid/nfc/tech/NdefFormatable;->TAG:Ljava/lang/String; -Landroid/nfc/tech/NfcA;-><init>(Landroid/nfc/Tag;)V -Landroid/nfc/tech/NfcA;->EXTRA_ATQA:Ljava/lang/String; -Landroid/nfc/tech/NfcA;->EXTRA_SAK:Ljava/lang/String; -Landroid/nfc/tech/NfcA;->mAtqa:[B -Landroid/nfc/tech/NfcA;->mSak:S -Landroid/nfc/tech/NfcA;->TAG:Ljava/lang/String; -Landroid/nfc/tech/NfcB;-><init>(Landroid/nfc/Tag;)V -Landroid/nfc/tech/NfcB;->EXTRA_APPDATA:Ljava/lang/String; -Landroid/nfc/tech/NfcB;->EXTRA_PROTINFO:Ljava/lang/String; -Landroid/nfc/tech/NfcB;->mAppData:[B -Landroid/nfc/tech/NfcB;->mProtInfo:[B -Landroid/nfc/tech/NfcBarcode;-><init>(Landroid/nfc/Tag;)V -Landroid/nfc/tech/NfcBarcode;->EXTRA_BARCODE_TYPE:Ljava/lang/String; -Landroid/nfc/tech/NfcBarcode;->mType:I -Landroid/nfc/tech/NfcF;-><init>(Landroid/nfc/Tag;)V -Landroid/nfc/tech/NfcF;->EXTRA_PMM:Ljava/lang/String; -Landroid/nfc/tech/NfcF;->EXTRA_SC:Ljava/lang/String; -Landroid/nfc/tech/NfcF;->mManufacturer:[B -Landroid/nfc/tech/NfcF;->mSystemCode:[B -Landroid/nfc/tech/NfcF;->TAG:Ljava/lang/String; -Landroid/nfc/tech/NfcV;-><init>(Landroid/nfc/Tag;)V -Landroid/nfc/tech/NfcV;->EXTRA_DSFID:Ljava/lang/String; -Landroid/nfc/tech/NfcV;->EXTRA_RESP_FLAGS:Ljava/lang/String; -Landroid/nfc/tech/NfcV;->mDsfId:B -Landroid/nfc/tech/NfcV;->mRespFlags:B -Landroid/nfc/tech/TagTechnology;->ISO_DEP:I -Landroid/nfc/tech/TagTechnology;->MIFARE_CLASSIC:I -Landroid/nfc/tech/TagTechnology;->MIFARE_ULTRALIGHT:I -Landroid/nfc/tech/TagTechnology;->NDEF:I -Landroid/nfc/tech/TagTechnology;->NDEF_FORMATABLE:I -Landroid/nfc/tech/TagTechnology;->NFC_A:I -Landroid/nfc/tech/TagTechnology;->NFC_B:I -Landroid/nfc/tech/TagTechnology;->NFC_BARCODE:I -Landroid/nfc/tech/TagTechnology;->NFC_F:I -Landroid/nfc/tech/TagTechnology;->NFC_V:I -Landroid/nfc/tech/TagTechnology;->reconnect()V -Landroid/nfc/TechListParcel;->CREATOR:Landroid/os/Parcelable$Creator; -Landroid/nfc/TechListParcel;->getTechLists()[[Ljava/lang/String; -Landroid/nfc/TechListParcel;->mTechLists:[[Ljava/lang/String; -Landroid/nfc/TransceiveResult;-><init>(I[B)V -Landroid/nfc/TransceiveResult;->CREATOR:Landroid/os/Parcelable$Creator; -Landroid/nfc/TransceiveResult;->getResponseOrThrow()[B -Landroid/nfc/TransceiveResult;->mResponseData:[B -Landroid/nfc/TransceiveResult;->mResult:I -Landroid/nfc/TransceiveResult;->RESULT_EXCEEDED_LENGTH:I -Landroid/nfc/TransceiveResult;->RESULT_FAILURE:I -Landroid/nfc/TransceiveResult;->RESULT_SUCCESS:I -Landroid/nfc/TransceiveResult;->RESULT_TAGLOST:I Landroid/opengl/EGL14;->eglCreatePbufferFromClientBuffer(Landroid/opengl/EGLDisplay;IJLandroid/opengl/EGLConfig;[II)Landroid/opengl/EGLSurface; Landroid/opengl/EGL14;->_eglCreateWindowSurface(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLConfig;Ljava/lang/Object;[II)Landroid/opengl/EGLSurface; Landroid/opengl/EGL14;->_eglCreateWindowSurfaceTexture(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLConfig;Ljava/lang/Object;[II)Landroid/opengl/EGLSurface; diff --git a/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt b/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt index f5184e7963d7..4df1dcaed136 100644 --- a/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt +++ b/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt @@ -19,7 +19,6 @@ Landroid/Manifest$permission;->READ_FRAME_BUFFER:Ljava/lang/String; Landroid/media/IVolumeController$Stub;->asInterface(Landroid/os/IBinder;)Landroid/media/IVolumeController; Landroid/net/INetworkPolicyListener$Stub;-><init>()V Landroid/net/sip/ISipSession$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/sip/ISipSession; -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_enable:I Landroid/os/IPowerManager$Stub;->TRANSACTION_acquireWakeLock:I Landroid/os/IPowerManager$Stub;->TRANSACTION_goToSleep:I Landroid/service/euicc/IEuiccService$Stub;-><init>()V diff --git a/core/api/current.txt b/core/api/current.txt index 46c8f8208883..b6e24a2000c3 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4625,9 +4625,9 @@ package android.app { public class ActivityManager { method public int addAppTask(@NonNull android.app.Activity, @NonNull android.content.Intent, @Nullable android.app.ActivityManager.TaskDescription, @NonNull android.graphics.Bitmap); + method @FlaggedApi("android.app.app_start_info") public void addApplicationStartInfoCompletionListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>); method @FlaggedApi("android.app.app_start_info") public void addStartInfoTimestamp(@IntRange(from=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START, to=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER) int, long); method public void appNotResponding(@NonNull String); - method @FlaggedApi("android.app.app_start_info") public void clearApplicationStartInfoCompletionListener(); method public boolean clearApplicationUserData(); method public void clearWatchHeapLimit(); method @RequiresPermission(android.Manifest.permission.DUMP) public void dumpPackageState(java.io.FileDescriptor, String); @@ -4661,8 +4661,8 @@ package android.app { method @RequiresPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) public void killBackgroundProcesses(String); method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int); method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int, android.os.Bundle); + method @FlaggedApi("android.app.app_start_info") public void removeApplicationStartInfoCompletionListener(@NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>); method @Deprecated public void restartPackage(String); - method @FlaggedApi("android.app.app_start_info") public void setApplicationStartInfoCompletionListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>); method public void setProcessStateSummary(@Nullable byte[]); method public static void setVrThread(int); method public void setWatchHeapLimit(long); @@ -10545,6 +10545,7 @@ package android.content { field public static final int BIND_INCLUDE_CAPABILITIES = 4096; // 0x1000 field public static final int BIND_NOT_FOREGROUND = 4; // 0x4 field public static final int BIND_NOT_PERCEPTIBLE = 256; // 0x100 + field @FlaggedApi("android.content.flags.enable_bind_package_isolated_process") public static final int BIND_PACKAGE_ISOLATED_PROCESS = 16384; // 0x4000 field public static final int BIND_SHARED_ISOLATED_PROCESS = 8192; // 0x2000 field public static final int BIND_WAIVE_PRIORITY = 32; // 0x20 field public static final String BIOMETRIC_SERVICE = "biometric"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index bd4ecf298d77..c7e314cd4438 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3229,6 +3229,7 @@ package android.companion.virtual { method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.input.VirtualMouseConfig); method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualNavigationTouchpad createVirtualNavigationTouchpad(@NonNull android.hardware.input.VirtualNavigationTouchpadConfig); + method @FlaggedApi("android.companion.virtual.flags.virtual_stylus") @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualStylus createVirtualStylus(@NonNull android.hardware.input.VirtualStylusConfig); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig); method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method public int getDeviceId(); @@ -5304,6 +5305,78 @@ package android.hardware.input { method @NonNull public android.hardware.input.VirtualNavigationTouchpadConfig build(); } + @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public class VirtualStylus implements java.io.Closeable { + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendButtonEvent(@NonNull android.hardware.input.VirtualStylusButtonEvent); + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendMotionEvent(@NonNull android.hardware.input.VirtualStylusMotionEvent); + } + + @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public final class VirtualStylusButtonEvent implements android.os.Parcelable { + method public int describeContents(); + method public int getAction(); + method public int getButtonCode(); + method public long getEventTimeNanos(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int ACTION_BUTTON_PRESS = 11; // 0xb + field public static final int ACTION_BUTTON_RELEASE = 12; // 0xc + field public static final int BUTTON_PRIMARY = 32; // 0x20 + field public static final int BUTTON_SECONDARY = 64; // 0x40 + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualStylusButtonEvent> CREATOR; + } + + @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public static final class VirtualStylusButtonEvent.Builder { + ctor public VirtualStylusButtonEvent.Builder(); + method @NonNull public android.hardware.input.VirtualStylusButtonEvent build(); + method @NonNull public android.hardware.input.VirtualStylusButtonEvent.Builder setAction(int); + method @NonNull public android.hardware.input.VirtualStylusButtonEvent.Builder setButtonCode(int); + method @NonNull public android.hardware.input.VirtualStylusButtonEvent.Builder setEventTimeNanos(long); + } + + @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public final class VirtualStylusConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable { + method public int describeContents(); + method public int getHeight(); + method public int getWidth(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualStylusConfig> CREATOR; + } + + @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public static final class VirtualStylusConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualStylusConfig.Builder> { + ctor public VirtualStylusConfig.Builder(@IntRange(from=1) int, @IntRange(from=1) int); + method @NonNull public android.hardware.input.VirtualStylusConfig build(); + } + + @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public final class VirtualStylusMotionEvent implements android.os.Parcelable { + method public int describeContents(); + method public int getAction(); + method public long getEventTimeNanos(); + method public int getPressure(); + method public int getTiltX(); + method public int getTiltY(); + method public int getToolType(); + method public int getX(); + method public int getY(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int ACTION_DOWN = 0; // 0x0 + field public static final int ACTION_MOVE = 2; // 0x2 + field public static final int ACTION_UP = 1; // 0x1 + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualStylusMotionEvent> CREATOR; + field public static final int TOOL_TYPE_ERASER = 4; // 0x4 + field public static final int TOOL_TYPE_STYLUS = 2; // 0x2 + } + + @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public static final class VirtualStylusMotionEvent.Builder { + ctor public VirtualStylusMotionEvent.Builder(); + method @NonNull public android.hardware.input.VirtualStylusMotionEvent build(); + method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setAction(int); + method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setEventTimeNanos(long); + method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setPressure(@IntRange(from=0x0, to=0xff) int); + method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setTiltX(@IntRange(from=0xffffffa6, to=0x5a) int); + method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setTiltY(@IntRange(from=0xffffffa6, to=0x5a) int); + method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setToolType(int); + method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setX(int); + method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setY(int); + } + public final class VirtualTouchEvent implements android.os.Parcelable { method public int describeContents(); method public int getAction(); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 6b7f4880e2f0..ebb5ba0e7b0f 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4052,10 +4052,28 @@ public class ActivityManager { } } + private final ArrayList<AppStartInfoCallbackWrapper> mAppStartInfoCallbacks = + new ArrayList<>(); + @Nullable + private IApplicationStartInfoCompleteListener mAppStartInfoCompleteListener = null; + + private static final class AppStartInfoCallbackWrapper { + @NonNull final Executor mExecutor; + @NonNull final Consumer<ApplicationStartInfo> mListener; + + AppStartInfoCallbackWrapper(@NonNull final Executor executor, + @NonNull final Consumer<ApplicationStartInfo> listener) { + mExecutor = executor; + mListener = listener; + } + } + /** - * Sets a callback to be notified when the {@link ApplicationStartInfo} records of this startup + * Adds a callback to be notified when the {@link ApplicationStartInfo} records of this startup * are complete. * + * <p class="note"> Note: callback will be removed automatically after being triggered.</p> + * * <p class="note"> Note: callback will not wait for {@link Activity#reportFullyDrawn} to occur. * Timestamp for fully drawn may be added after callback occurs. Set callback after invoking * {@link Activity#reportFullyDrawn} if timestamp for fully drawn is required.</p> @@ -4073,33 +4091,77 @@ public class ActivityManager { * @throws IllegalArgumentException if executor or listener are null. */ @FlaggedApi(Flags.FLAG_APP_START_INFO) - public void setApplicationStartInfoCompletionListener(@NonNull final Executor executor, + public void addApplicationStartInfoCompletionListener(@NonNull final Executor executor, @NonNull final Consumer<ApplicationStartInfo> listener) { Preconditions.checkNotNull(executor, "executor cannot be null"); Preconditions.checkNotNull(listener, "listener cannot be null"); - IApplicationStartInfoCompleteListener callback = - new IApplicationStartInfoCompleteListener.Stub() { - @Override - public void onApplicationStartInfoComplete(ApplicationStartInfo applicationStartInfo) { - executor.execute(() -> listener.accept(applicationStartInfo)); + synchronized (mAppStartInfoCallbacks) { + for (int i = 0; i < mAppStartInfoCallbacks.size(); i++) { + if (listener.equals(mAppStartInfoCallbacks.get(i).mListener)) { + return; + } + } + if (mAppStartInfoCompleteListener == null) { + mAppStartInfoCompleteListener = new IApplicationStartInfoCompleteListener.Stub() { + @Override + public void onApplicationStartInfoComplete( + ApplicationStartInfo applicationStartInfo) { + synchronized (mAppStartInfoCallbacks) { + for (int i = 0; i < mAppStartInfoCallbacks.size(); i++) { + final AppStartInfoCallbackWrapper callback = + mAppStartInfoCallbacks.get(i); + callback.mExecutor.execute(() -> callback.mListener.accept( + applicationStartInfo)); + } + mAppStartInfoCallbacks.clear(); + mAppStartInfoCompleteListener = null; + } + } + }; + boolean succeeded = false; + try { + getService().addApplicationStartInfoCompleteListener( + mAppStartInfoCompleteListener, mContext.getUserId()); + succeeded = true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (succeeded) { + mAppStartInfoCallbacks.add(new AppStartInfoCallbackWrapper(executor, listener)); + } else { + mAppStartInfoCompleteListener = null; + mAppStartInfoCallbacks.clear(); + } + } else { + mAppStartInfoCallbacks.add(new AppStartInfoCallbackWrapper(executor, listener)); } - }; - try { - getService().setApplicationStartInfoCompleteListener(callback, mContext.getUserId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); } } /** - * Removes the callback set by {@link #setApplicationStartInfoCompletionListener} if there is one. + * Removes the provided callback set by {@link #addApplicationStartInfoCompletionListener}. */ @FlaggedApi(Flags.FLAG_APP_START_INFO) - public void clearApplicationStartInfoCompletionListener() { - try { - getService().clearApplicationStartInfoCompleteListener(mContext.getUserId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + public void removeApplicationStartInfoCompletionListener( + @NonNull final Consumer<ApplicationStartInfo> listener) { + Preconditions.checkNotNull(listener, "listener cannot be null"); + synchronized (mAppStartInfoCallbacks) { + for (int i = 0; i < mAppStartInfoCallbacks.size(); i++) { + final AppStartInfoCallbackWrapper callback = mAppStartInfoCallbacks.get(i); + if (listener.equals(callback.mListener)) { + mAppStartInfoCallbacks.remove(i); + break; + } + } + if (mAppStartInfoCompleteListener != null && mAppStartInfoCallbacks.isEmpty()) { + try { + getService().removeApplicationStartInfoCompleteListener( + mAppStartInfoCompleteListener, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mAppStartInfoCompleteListener = null; + } } } diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java index 656feb0401d6..cc3eac1bbf01 100644 --- a/core/java/android/app/ApplicationStartInfo.java +++ b/core/java/android/app/ApplicationStartInfo.java @@ -39,12 +39,17 @@ import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Iterator; import java.util.Map; -import java.util.Set; /** - * Provide information related to a processes startup. + * Describes information related to an application process's startup. + * + * <p> + * Many aspects concerning why and how an applications process was started are valuable for apps + * both for logging and for potential behavior changes. Reason for process start, start type, + * start times, throttling, and other useful diagnostic data can be obtained from + * {@link ApplicationStartInfo} records. + * </p> */ @FlaggedApi(Flags.FLAG_APP_START_INFO) public final class ApplicationStartInfo implements Parcelable { @@ -552,13 +557,12 @@ public final class ApplicationStartInfo implements Parcelable { dest.writeInt(mDefiningUid); dest.writeString(mProcessName); dest.writeInt(mReason); - dest.writeInt(mStartupTimestampsNs.size()); - Set<Map.Entry<Integer, Long>> timestampEntrySet = mStartupTimestampsNs.entrySet(); - Iterator<Map.Entry<Integer, Long>> iter = timestampEntrySet.iterator(); - while (iter.hasNext()) { - Map.Entry<Integer, Long> entry = iter.next(); - dest.writeInt(entry.getKey()); - dest.writeLong(entry.getValue()); + dest.writeInt(mStartupTimestampsNs == null ? 0 : mStartupTimestampsNs.size()); + if (mStartupTimestampsNs != null) { + for (int i = 0; i < mStartupTimestampsNs.size(); i++) { + dest.writeInt(mStartupTimestampsNs.keyAt(i)); + dest.writeLong(mStartupTimestampsNs.valueAt(i)); + } } dest.writeInt(mStartType); dest.writeParcelable(mStartIntent, flags); @@ -740,13 +744,11 @@ public final class ApplicationStartInfo implements Parcelable { sb.append(" intent=").append(mStartIntent.toString()) .append('\n'); } - if (mStartupTimestampsNs.size() > 0) { + if (mStartupTimestampsNs != null && mStartupTimestampsNs.size() > 0) { sb.append(" timestamps: "); - Set<Map.Entry<Integer, Long>> timestampEntrySet = mStartupTimestampsNs.entrySet(); - Iterator<Map.Entry<Integer, Long>> iter = timestampEntrySet.iterator(); - while (iter.hasNext()) { - Map.Entry<Integer, Long> entry = iter.next(); - sb.append(entry.getKey()).append("=").append(entry.getValue()).append(" "); + for (int i = 0; i < mStartupTimestampsNs.size(); i++) { + sb.append(mStartupTimestampsNs.keyAt(i)).append("=").append(mStartupTimestampsNs + .valueAt(i)).append(" "); } sb.append('\n'); } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 260e9859c72d..139275477063 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -715,7 +715,7 @@ interface IActivityManager { * @param listener A listener to for the callback upon completion of startup data collection. * @param userId The userId in the multi-user environment. */ - void setApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener, + void addApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener, int userId); @@ -724,7 +724,8 @@ interface IActivityManager { * * @param userId The userId in the multi-user environment. */ - void clearApplicationStartInfoCompleteListener(int userId); + void removeApplicationStartInfoCompleteListener(IApplicationStartInfoCompleteListener listener, + int userId); /** diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 12229b12fb16..6eab363c4eb1 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -35,6 +35,9 @@ import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseConfig; import android.hardware.input.VirtualMouseRelativeEvent; import android.hardware.input.VirtualMouseScrollEvent; +import android.hardware.input.VirtualStylusButtonEvent; +import android.hardware.input.VirtualStylusConfig; +import android.hardware.input.VirtualStylusMotionEvent; import android.hardware.input.VirtualTouchEvent; import android.hardware.input.VirtualTouchscreenConfig; import android.hardware.input.VirtualNavigationTouchpadConfig; @@ -144,6 +147,12 @@ interface IVirtualDevice { void createVirtualNavigationTouchpad(in VirtualNavigationTouchpadConfig config, IBinder token); /** + * Creates a new stylus and registers it with the input framework with the given token. + */ + @EnforcePermission("CREATE_VIRTUAL_DEVICE") + void createVirtualStylus(in VirtualStylusConfig config, IBinder token); + + /** * Removes the input device corresponding to the given token from the framework. */ @EnforcePermission("CREATE_VIRTUAL_DEVICE") @@ -156,32 +165,32 @@ interface IVirtualDevice { int getInputDeviceId(IBinder token); /** - * Injects a key event to the virtual dpad corresponding to the given token. - */ + * Injects a key event to the virtual dpad corresponding to the given token. + */ @EnforcePermission("CREATE_VIRTUAL_DEVICE") boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event); /** - * Injects a key event to the virtual keyboard corresponding to the given token. - */ + * Injects a key event to the virtual keyboard corresponding to the given token. + */ @EnforcePermission("CREATE_VIRTUAL_DEVICE") boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event); /** - * Injects a button event to the virtual mouse corresponding to the given token. - */ + * Injects a button event to the virtual mouse corresponding to the given token. + */ @EnforcePermission("CREATE_VIRTUAL_DEVICE") boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event); /** - * Injects a relative event to the virtual mouse corresponding to the given token. - */ + * Injects a relative event to the virtual mouse corresponding to the given token. + */ @EnforcePermission("CREATE_VIRTUAL_DEVICE") boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event); /** - * Injects a scroll event to the virtual mouse corresponding to the given token. - */ + * Injects a scroll event to the virtual mouse corresponding to the given token. + */ @EnforcePermission("CREATE_VIRTUAL_DEVICE") boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event); @@ -192,6 +201,18 @@ interface IVirtualDevice { boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event); /** + * Injects a motion event from the virtual stylus input device corresponding to the given token. + */ + @EnforcePermission("CREATE_VIRTUAL_DEVICE") + boolean sendStylusMotionEvent(IBinder token, in VirtualStylusMotionEvent event); + + /** + * Injects a button event from the virtual stylus input device corresponding to the given token. + */ + @EnforcePermission("CREATE_VIRTUAL_DEVICE") + boolean sendStylusButtonEvent(IBinder token, in VirtualStylusButtonEvent event); + + /** * Returns all virtual sensors created for this device. */ @EnforcePermission("CREATE_VIRTUAL_DEVICE") diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java index 2abeeeecc1c6..c1e443d1729d 100644 --- a/core/java/android/companion/virtual/VirtualDeviceInternal.java +++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java @@ -42,6 +42,8 @@ import android.hardware.input.VirtualMouse; import android.hardware.input.VirtualMouseConfig; import android.hardware.input.VirtualNavigationTouchpad; import android.hardware.input.VirtualNavigationTouchpadConfig; +import android.hardware.input.VirtualStylus; +import android.hardware.input.VirtualStylusConfig; import android.hardware.input.VirtualTouchscreen; import android.hardware.input.VirtualTouchscreenConfig; import android.media.AudioManager; @@ -316,6 +318,19 @@ public class VirtualDeviceInternal { } } + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @NonNull + VirtualStylus createVirtualStylus(@NonNull VirtualStylusConfig config) { + try { + final IBinder token = new Binder( + "android.hardware.input.VirtualStylus:" + config.getInputDeviceName()); + mVirtualDevice.createVirtualStylus(config, token); + return new VirtualStylus(config, mVirtualDevice, token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @NonNull VirtualNavigationTouchpad createVirtualNavigationTouchpad( @NonNull VirtualNavigationTouchpadConfig config) { diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index eef60f11fb1c..10b652a382df 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -55,6 +55,8 @@ import android.hardware.input.VirtualMouse; import android.hardware.input.VirtualMouseConfig; import android.hardware.input.VirtualNavigationTouchpad; import android.hardware.input.VirtualNavigationTouchpadConfig; +import android.hardware.input.VirtualStylus; +import android.hardware.input.VirtualStylusConfig; import android.hardware.input.VirtualTouchscreen; import android.hardware.input.VirtualTouchscreenConfig; import android.media.AudioManager; @@ -859,6 +861,19 @@ public final class VirtualDeviceManager { } /** + * Creates a virtual stylus. + * + * @param config the touchscreen configurations for the virtual stylus. + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @NonNull + @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS) + public VirtualStylus createVirtualStylus( + @NonNull VirtualStylusConfig config) { + return mVirtualDeviceInternal.createVirtualStylus(config); + } + + /** * Creates a VirtualAudioDevice, capable of recording audio emanating from this device, * or injecting audio from another device. * diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index ce2490b8efb8..588e4fce1f3d 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -100,3 +100,10 @@ flag { description: "Enable interactive screen mirroring using Virtual Devices" bug: "292212199" } + +flag { + name: "virtual_stylus" + namespace: "virtual_devices" + description: "Enable virtual stylus input" + bug: "304829446" +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index fa76e3976a58..31c7c782edd3 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -16,6 +16,8 @@ package android.content; +import static android.content.flags.Flags.FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS; + import android.annotation.AttrRes; import android.annotation.CallbackExecutor; import android.annotation.CheckResult; @@ -296,6 +298,7 @@ public abstract class Context { BIND_ALLOW_ACTIVITY_STARTS, BIND_INCLUDE_CAPABILITIES, BIND_SHARED_ISOLATED_PROCESS, + BIND_PACKAGE_ISOLATED_PROCESS, BIND_EXTERNAL_SERVICE }) @Retention(RetentionPolicy.SOURCE) @@ -318,6 +321,7 @@ public abstract class Context { BIND_ALLOW_ACTIVITY_STARTS, BIND_INCLUDE_CAPABILITIES, BIND_SHARED_ISOLATED_PROCESS, + BIND_PACKAGE_ISOLATED_PROCESS, // Intentionally not include BIND_EXTERNAL_SERVICE, because it'd cause sign-extension. // This would allow Android Studio to show a warning, if someone tries to use // BIND_EXTERNAL_SERVICE BindServiceFlags. @@ -511,6 +515,26 @@ public abstract class Context { */ public static final int BIND_SHARED_ISOLATED_PROCESS = 0x00002000; + /** + * Flag for {@link #bindIsolatedService}: Bind the service into a shared isolated process, + * but only with other isolated services from the same package that declare the same process + * name. + * + * <p>Specifying this flag allows multiple isolated services defined in the same package to be + * running in a single shared isolated process. This shared isolated process must be specified + * since this flag will not work with the default application process. + * + * <p>This flag is different from {@link #BIND_SHARED_ISOLATED_PROCESS} since it only + * allows binding services from the same package in the same shared isolated process. This also + * means the shared package isolated process is global, and not scoped to each potential + * calling app. + * + * <p>The shared isolated process instance is identified by the "android:process" attribute + * defined by the service. This flag cannot be used without this attribute set. + */ + @FlaggedApi(FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS) + public static final int BIND_PACKAGE_ISOLATED_PROCESS = 1 << 14; + /*********** Public flags above this line ***********/ /*********** Hidden flags below this line ***********/ diff --git a/core/java/android/content/flags/flags.aconfig b/core/java/android/content/flags/flags.aconfig new file mode 100644 index 000000000000..3445fb53d307 --- /dev/null +++ b/core/java/android/content/flags/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.content.flags" + +flag { + name: "enable_bind_package_isolated_process" + namespace: "machine_learning" + description: "This flag enables the newly added flag for binding package-private isolated processes." + bug: "312706530" +}
\ No newline at end of file diff --git a/core/java/android/hardware/input/VirtualStylus.java b/core/java/android/hardware/input/VirtualStylus.java new file mode 100644 index 000000000000..c763f7406f3a --- /dev/null +++ b/core/java/android/hardware/input/VirtualStylus.java @@ -0,0 +1,80 @@ +/* + * Copyright 2023 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.input; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.flags.Flags; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +/** + * A virtual stylus which can be used to inject input into the framework that represents a stylus + * on a remote device. + * + * This registers an {@link android.view.InputDevice} that is interpreted like a + * physically-connected device and dispatches received events to it. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS) +@SystemApi +public class VirtualStylus extends VirtualInputDevice { + /** @hide */ + public VirtualStylus(VirtualStylusConfig config, IVirtualDevice virtualDevice, + IBinder token) { + super(config, virtualDevice, token); + } + + /** + * Sends a motion event to the system. + * + * @param event the event to send + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void sendMotionEvent(@NonNull VirtualStylusMotionEvent event) { + try { + if (!mVirtualDevice.sendStylusMotionEvent(mToken, event)) { + Log.w(TAG, "Failed to send motion event from virtual stylus " + + mConfig.getInputDeviceName()); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sends a button event to the system. + * + * @param event the event to send + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void sendButtonEvent(@NonNull VirtualStylusButtonEvent event) { + try { + if (!mVirtualDevice.sendStylusButtonEvent(mToken, event)) { + Log.w(TAG, "Failed to send button event from virtual stylus " + + mConfig.getInputDeviceName()); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/hardware/input/VirtualStylusButtonEvent.aidl b/core/java/android/hardware/input/VirtualStylusButtonEvent.aidl new file mode 100644 index 000000000000..7de32cc7d33e --- /dev/null +++ b/core/java/android/hardware/input/VirtualStylusButtonEvent.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2023 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.input; + +parcelable VirtualStylusButtonEvent; diff --git a/core/java/android/hardware/input/VirtualStylusButtonEvent.java b/core/java/android/hardware/input/VirtualStylusButtonEvent.java new file mode 100644 index 000000000000..97a4cd0f692b --- /dev/null +++ b/core/java/android/hardware/input/VirtualStylusButtonEvent.java @@ -0,0 +1,215 @@ +/* + * Copyright 2023 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.input; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.companion.virtual.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.view.InputEvent; +import android.view.MotionEvent; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * An event describing a stylus button click interaction originating from a remote device. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS) +@SystemApi +public final class VirtualStylusButtonEvent implements Parcelable { + /** @hide */ + public static final int ACTION_UNKNOWN = -1; + /** Action indicating the stylus button has been pressed. */ + public static final int ACTION_BUTTON_PRESS = MotionEvent.ACTION_BUTTON_PRESS; + /** Action indicating the stylus button has been released. */ + public static final int ACTION_BUTTON_RELEASE = MotionEvent.ACTION_BUTTON_RELEASE; + /** @hide */ + @IntDef(prefix = {"ACTION_"}, value = { + ACTION_UNKNOWN, + ACTION_BUTTON_PRESS, + ACTION_BUTTON_RELEASE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Action {} + + /** @hide */ + public static final int BUTTON_UNKNOWN = -1; + /** Action indicating the stylus button involved in this event is primary. */ + public static final int BUTTON_PRIMARY = MotionEvent.BUTTON_STYLUS_PRIMARY; + /** Action indicating the stylus button involved in this event is secondary. */ + public static final int BUTTON_SECONDARY = MotionEvent.BUTTON_STYLUS_SECONDARY; + /** @hide */ + @IntDef(prefix = {"BUTTON_"}, value = { + BUTTON_UNKNOWN, + BUTTON_PRIMARY, + BUTTON_SECONDARY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Button {} + + @Action + private final int mAction; + @Button + private final int mButtonCode; + private final long mEventTimeNanos; + + private VirtualStylusButtonEvent(@Action int action, @Button int buttonCode, + long eventTimeNanos) { + mAction = action; + mButtonCode = buttonCode; + mEventTimeNanos = eventTimeNanos; + } + + private VirtualStylusButtonEvent(@NonNull Parcel parcel) { + mAction = parcel.readInt(); + mButtonCode = parcel.readInt(); + mEventTimeNanos = parcel.readLong(); + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) { + parcel.writeInt(mAction); + parcel.writeInt(mButtonCode); + parcel.writeLong(mEventTimeNanos); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Returns the button code associated with this event. + */ + @Button + public int getButtonCode() { + return mButtonCode; + } + + /** + * Returns the action associated with this event. + */ + @Action + public int getAction() { + return mAction; + } + + /** + * Returns the time this event occurred, in the {@link SystemClock#uptimeMillis()} time base but + * with nanosecond (instead of millisecond) precision. + * + * @see InputEvent#getEventTime() + */ + public long getEventTimeNanos() { + return mEventTimeNanos; + } + + /** + * Builder for {@link VirtualStylusButtonEvent}. + */ + @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS) + public static final class Builder { + + @Action + private int mAction = ACTION_UNKNOWN; + @Button + private int mButtonCode = BUTTON_UNKNOWN; + private long mEventTimeNanos = 0L; + + /** + * Creates a {@link VirtualStylusButtonEvent} object with the current builder configuration. + */ + @NonNull + public VirtualStylusButtonEvent build() { + if (mAction == ACTION_UNKNOWN) { + throw new IllegalArgumentException( + "Cannot build stylus button event with unset action"); + } + if (mButtonCode == BUTTON_UNKNOWN) { + throw new IllegalArgumentException( + "Cannot build stylus button event with unset button code"); + } + return new VirtualStylusButtonEvent(mAction, mButtonCode, mEventTimeNanos); + } + + /** + * Sets the button code of the event. + * + * @return this builder, to allow for chaining of calls + */ + @NonNull + public Builder setButtonCode(@Button int buttonCode) { + if (buttonCode != BUTTON_PRIMARY && buttonCode != BUTTON_SECONDARY) { + throw new IllegalArgumentException( + "Unsupported stylus button code : " + buttonCode); + } + mButtonCode = buttonCode; + return this; + } + + /** + * Sets the action of the event. + * + * @return this builder, to allow for chaining of calls + */ + @NonNull + public Builder setAction(@Action int action) { + if (action != ACTION_BUTTON_PRESS && action != ACTION_BUTTON_RELEASE) { + throw new IllegalArgumentException("Unsupported stylus button action : " + action); + } + mAction = action; + return this; + } + + /** + * Sets the time (in nanoseconds) when this specific event was generated. This may be + * obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of + * millisecond), but can be different depending on the use case. + * This field is optional and can be omitted. + * + * @return this builder, to allow for chaining of calls + * @see InputEvent#getEventTime() + */ + @NonNull + public Builder setEventTimeNanos(long eventTimeNanos) { + if (eventTimeNanos < 0L) { + throw new IllegalArgumentException("Event time cannot be negative"); + } + this.mEventTimeNanos = eventTimeNanos; + return this; + } + } + + @NonNull + public static final Parcelable.Creator<VirtualStylusButtonEvent> CREATOR = + new Parcelable.Creator<>() { + public VirtualStylusButtonEvent createFromParcel(Parcel source) { + return new VirtualStylusButtonEvent(source); + } + + public VirtualStylusButtonEvent[] newArray(int size) { + return new VirtualStylusButtonEvent[size]; + } + }; +} diff --git a/core/java/android/hardware/input/VirtualStylusConfig.aidl b/core/java/android/hardware/input/VirtualStylusConfig.aidl new file mode 100644 index 000000000000..a13eec2a43b5 --- /dev/null +++ b/core/java/android/hardware/input/VirtualStylusConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2023 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.input; + +parcelable VirtualStylusConfig; diff --git a/core/java/android/hardware/input/VirtualStylusConfig.java b/core/java/android/hardware/input/VirtualStylusConfig.java new file mode 100644 index 000000000000..64cf1f56d8bc --- /dev/null +++ b/core/java/android/hardware/input/VirtualStylusConfig.java @@ -0,0 +1,98 @@ +/* + * Copyright 2023 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.input; + +import android.annotation.FlaggedApi; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.companion.virtual.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Configurations to create a virtual stylus. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS) +@SystemApi +public final class VirtualStylusConfig extends VirtualTouchDeviceConfig implements Parcelable { + + private VirtualStylusConfig(@NonNull Builder builder) { + super(builder); + } + + private VirtualStylusConfig(@NonNull Parcel in) { + super(in); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + super.writeToParcel(dest, flags); + } + + @NonNull + public static final Creator<VirtualStylusConfig> CREATOR = + new Creator<>() { + @Override + public VirtualStylusConfig createFromParcel(Parcel in) { + return new VirtualStylusConfig(in); + } + + @Override + public VirtualStylusConfig[] newArray(int size) { + return new VirtualStylusConfig[size]; + } + }; + + /** + * Builder for creating a {@link VirtualStylusConfig}. + */ + @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS) + public static final class Builder extends VirtualTouchDeviceConfig.Builder<Builder> { + + /** + * Creates a new instance for the given dimensions of the screen targeted by the + * {@link VirtualStylus}. + * + * <p>The dimensions are not pixels but in the screen's raw coordinate space. They do + * not necessarily have to correspond to the display size or aspect ratio. In this case the + * framework will handle the scaling appropriately. + * + * @param screenWidth The width of the targeted screen. + * @param screenHeight The height of the targeted screen. + */ + public Builder(@IntRange(from = 1) int screenWidth, + @IntRange(from = 1) int screenHeight) { + super(screenWidth, screenHeight); + } + + /** + * Builds the {@link VirtualStylusConfig} instance. + */ + @NonNull + public VirtualStylusConfig build() { + return new VirtualStylusConfig(this); + } + } +} diff --git a/core/java/android/hardware/input/VirtualStylusMotionEvent.aidl b/core/java/android/hardware/input/VirtualStylusMotionEvent.aidl new file mode 100644 index 000000000000..42d14ab35b90 --- /dev/null +++ b/core/java/android/hardware/input/VirtualStylusMotionEvent.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2023 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.input; + +parcelable VirtualStylusMotionEvent; diff --git a/core/java/android/hardware/input/VirtualStylusMotionEvent.java b/core/java/android/hardware/input/VirtualStylusMotionEvent.java new file mode 100644 index 000000000000..2ab76aee74a4 --- /dev/null +++ b/core/java/android/hardware/input/VirtualStylusMotionEvent.java @@ -0,0 +1,411 @@ +/* + * Copyright 2023 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.input; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.companion.virtual.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.view.InputEvent; +import android.view.MotionEvent; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * An event describing a stylus interaction originating from a remote device. + * + * The tool type, location and action are required; tilts and pressure are optional. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS) +@SystemApi +public final class VirtualStylusMotionEvent implements Parcelable { + private static final int TILT_MIN = -90; + private static final int TILT_MAX = 90; + private static final int PRESSURE_MIN = 0; + private static final int PRESSURE_MAX = 255; + + /** @hide */ + public static final int TOOL_TYPE_UNKNOWN = MotionEvent.TOOL_TYPE_UNKNOWN; + /** Tool type indicating that a stylus is the origin of the event. */ + public static final int TOOL_TYPE_STYLUS = MotionEvent.TOOL_TYPE_STYLUS; + /** Tool type indicating that an eraser is the origin of the event. */ + public static final int TOOL_TYPE_ERASER = MotionEvent.TOOL_TYPE_ERASER; + /** @hide */ + @IntDef(prefix = { "TOOL_TYPE_" }, value = { + TOOL_TYPE_UNKNOWN, + TOOL_TYPE_STYLUS, + TOOL_TYPE_ERASER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ToolType {} + + /** @hide */ + public static final int ACTION_UNKNOWN = -1; + /** + * Action indicating the stylus has been pressed down to the screen. ACTION_DOWN with pressure + * {@code 0} indicates that the stylus is hovering over the screen, and non-zero pressure + * indicates that the stylus is touching the screen. + */ + public static final int ACTION_DOWN = MotionEvent.ACTION_DOWN; + /** Action indicating the stylus has been lifted from the screen. */ + public static final int ACTION_UP = MotionEvent.ACTION_UP; + /** Action indicating the stylus has been moved along the screen. */ + public static final int ACTION_MOVE = MotionEvent.ACTION_MOVE; + /** @hide */ + @IntDef(prefix = { "ACTION_" }, value = { + ACTION_UNKNOWN, + ACTION_DOWN, + ACTION_UP, + ACTION_MOVE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Action {} + + @ToolType + private final int mToolType; + @Action + private final int mAction; + private final int mX; + private final int mY; + private final int mPressure; + private final int mTiltX; + private final int mTiltY; + private final long mEventTimeNanos; + + private VirtualStylusMotionEvent(@ToolType int toolType, @Action int action, int x, int y, + int pressure, int tiltX, int tiltY, long eventTimeNanos) { + mToolType = toolType; + mAction = action; + mX = x; + mY = y; + mPressure = pressure; + mTiltX = tiltX; + mTiltY = tiltY; + mEventTimeNanos = eventTimeNanos; + } + + private VirtualStylusMotionEvent(@NonNull Parcel parcel) { + mToolType = parcel.readInt(); + mAction = parcel.readInt(); + mX = parcel.readInt(); + mY = parcel.readInt(); + mPressure = parcel.readInt(); + mTiltX = parcel.readInt(); + mTiltY = parcel.readInt(); + mEventTimeNanos = parcel.readLong(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mToolType); + dest.writeInt(mAction); + dest.writeInt(mX); + dest.writeInt(mY); + dest.writeInt(mPressure); + dest.writeInt(mTiltX); + dest.writeInt(mTiltY); + dest.writeLong(mEventTimeNanos); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Returns the tool type associated with this event. + */ + @ToolType + public int getToolType() { + return mToolType; + } + + /** + * Returns the action associated with this event. + */ + @Action + public int getAction() { + return mAction; + } + + /** + * Returns the x-axis location associated with this event. + */ + public int getX() { + return mX; + } + + /** + * Returns the y-axis location associated with this event. + */ + public int getY() { + return mY; + } + + /** + * Returns the pressure associated with this event. {@code 0} pressure indicates that the stylus + * is hovering, otherwise the stylus is touching the screen. Returns {@code 255} if omitted. + */ + public int getPressure() { + return mPressure; + } + + /** + * Returns the plane angle (in degrees, in the range of [{@code -90}, {@code 90}]) between the + * y-z plane and the plane containing both the stylus axis and the y axis. A positive tiltX is + * to the right, in the direction of increasing x values. {@code 0} tilt indicates that the + * stylus is perpendicular to the x-axis. Returns {@code 0} if omitted. + * + * @see Builder#setTiltX + */ + public int getTiltX() { + return mTiltX; + } + + /** + * Returns the plane angle (in degrees, in the range of [{@code -90}, {@code 90}]) between the + * x-z plane and the plane containing both the stylus axis and the x axis. A positive tiltY is + * towards the user, in the direction of increasing y values. {@code 0} tilt indicates that the + * stylus is perpendicular to the y-axis. Returns {@code 0} if omitted. + * + * @see Builder#setTiltY + */ + public int getTiltY() { + return mTiltY; + } + + /** + * Returns the time this event occurred, in the {@link SystemClock#uptimeMillis()} time base but + * with nanosecond (instead of millisecond) precision. + * + * @see InputEvent#getEventTime() + */ + public long getEventTimeNanos() { + return mEventTimeNanos; + } + + /** + * Builder for {@link VirtualStylusMotionEvent}. + */ + @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS) + public static final class Builder { + + @ToolType + private int mToolType = TOOL_TYPE_UNKNOWN; + @Action + private int mAction = ACTION_UNKNOWN; + private int mX = 0; + private int mY = 0; + private boolean mIsXSet = false; + private boolean mIsYSet = false; + private int mPressure = PRESSURE_MAX; + private int mTiltX = 0; + private int mTiltY = 0; + private long mEventTimeNanos = 0L; + + /** + * Creates a {@link VirtualStylusMotionEvent} object with the current builder configuration. + * + * @throws IllegalArgumentException if one of the required arguments (action, tool type, + * x-axis location and y-axis location) is missing. + * {@link VirtualStylusMotionEvent} for a detailed explanation. + */ + @NonNull + public VirtualStylusMotionEvent build() { + if (mToolType == TOOL_TYPE_UNKNOWN) { + throw new IllegalArgumentException( + "Cannot build stylus motion event with unset tool type"); + } + if (mAction == ACTION_UNKNOWN) { + throw new IllegalArgumentException( + "Cannot build stylus motion event with unset action"); + } + if (!mIsXSet) { + throw new IllegalArgumentException( + "Cannot build stylus motion event with unset x-axis location"); + } + if (!mIsYSet) { + throw new IllegalArgumentException( + "Cannot build stylus motion event with unset y-axis location"); + } + return new VirtualStylusMotionEvent(mToolType, mAction, mX, mY, mPressure, mTiltX, + mTiltY, mEventTimeNanos); + } + + /** + * Sets the tool type of the event. + * + * @return this builder, to allow for chaining of calls + */ + @NonNull + public Builder setToolType(@ToolType int toolType) { + if (toolType != TOOL_TYPE_STYLUS && toolType != TOOL_TYPE_ERASER) { + throw new IllegalArgumentException("Unsupported stylus tool type: " + toolType); + } + mToolType = toolType; + return this; + } + + /** + * Sets the action of the event. + * + * @return this builder, to allow for chaining of calls + */ + @NonNull + public Builder setAction(@Action int action) { + if (action != ACTION_DOWN && action != ACTION_UP && action != ACTION_MOVE) { + throw new IllegalArgumentException("Unsupported stylus action : " + action); + } + mAction = action; + return this; + } + + /** + * Sets the x-axis location of the event. + * + * @return this builder, to allow for chaining of calls + */ + @NonNull + public Builder setX(int absX) { + mX = absX; + mIsXSet = true; + return this; + } + + /** + * Sets the y-axis location of the event. + * + * @return this builder, to allow for chaining of calls + */ + @NonNull + public Builder setY(int absY) { + mY = absY; + mIsYSet = true; + return this; + } + + /** + * Sets the pressure of the event. {@code 0} pressure indicates that the stylus is hovering, + * otherwise the stylus is touching the screen. This field is optional and can be omitted + * (defaults to {@code 255}). + * + * @param pressure The pressure of the stylus. + * + * @throws IllegalArgumentException if the pressure is smaller than 0 or greater than 255. + * + * @return this builder, to allow for chaining of calls + */ + @NonNull + public Builder setPressure( + @IntRange(from = PRESSURE_MIN, to = PRESSURE_MAX) int pressure) { + if (pressure < PRESSURE_MIN || pressure > PRESSURE_MAX) { + throw new IllegalArgumentException( + "Pressure should be between " + PRESSURE_MIN + " and " + PRESSURE_MAX); + } + mPressure = pressure; + return this; + } + + /** + * Sets the x-axis tilt of the event in degrees. {@code 0} tilt indicates that the stylus is + * perpendicular to the x-axis. This field is optional and can be omitted (defaults to + * {@code 0}). Both x-axis tilt and y-axis tilt are used to derive the tilt and orientation + * of the stylus, given by {@link MotionEvent#AXIS_TILT} and + * {@link MotionEvent#AXIS_ORIENTATION} respectively. + * + * @throws IllegalArgumentException if the tilt is smaller than -90 or greater than 90. + * + * @return this builder, to allow for chaining of calls + * + * @see VirtualStylusMotionEvent#getTiltX + * @see <a href="https://source.android.com/docs/core/interaction/input/touch-devices#orientation-and-tilt-fields"> + * Stylus tilt and orientation</a> + */ + @NonNull + public Builder setTiltX(@IntRange(from = TILT_MIN, to = TILT_MAX) int tiltX) { + validateTilt(tiltX); + mTiltX = tiltX; + return this; + } + + /** + * Sets the y-axis tilt of the event in degrees. {@code 0} tilt indicates that the stylus is + * perpendicular to the y-axis. This field is optional and can be omitted (defaults to + * {@code 0}). Both x-axis tilt and y-axis tilt are used to derive the tilt and orientation + * of the stylus, given by {@link MotionEvent#AXIS_TILT} and + * {@link MotionEvent#AXIS_ORIENTATION} respectively. + * + * @throws IllegalArgumentException if the tilt is smaller than -90 or greater than 90. + * + * @return this builder, to allow for chaining of calls + * + * @see VirtualStylusMotionEvent#getTiltY + * @see <a href="https://source.android.com/docs/core/interaction/input/touch-devices#orientation-and-tilt-fields"> + * Stylus tilt and orientation</a> + */ + @NonNull + public Builder setTiltY(@IntRange(from = TILT_MIN, to = TILT_MAX) int tiltY) { + validateTilt(tiltY); + mTiltY = tiltY; + return this; + } + + /** + * Sets the time (in nanoseconds) when this specific event was generated. This may be + * obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of + * millisecond), but can be different depending on the use case. + * This field is optional and can be omitted. + * + * @return this builder, to allow for chaining of calls + * @see InputEvent#getEventTime() + */ + @NonNull + public Builder setEventTimeNanos(long eventTimeNanos) { + if (eventTimeNanos < 0L) { + throw new IllegalArgumentException("Event time cannot be negative"); + } + mEventTimeNanos = eventTimeNanos; + return this; + } + + private void validateTilt(int tilt) { + if (tilt < TILT_MIN || tilt > TILT_MAX) { + throw new IllegalArgumentException( + "Tilt must be between " + TILT_MIN + " and " + TILT_MAX); + } + } + } + + @NonNull + public static final Parcelable.Creator<VirtualStylusMotionEvent> CREATOR = + new Parcelable.Creator<>() { + public VirtualStylusMotionEvent createFromParcel(Parcel source) { + return new VirtualStylusMotionEvent(source); + } + public VirtualStylusMotionEvent[] newArray(int size) { + return new VirtualStylusMotionEvent[size]; + } + }; +} diff --git a/core/java/android/hardware/input/VirtualTouchDeviceConfig.java b/core/java/android/hardware/input/VirtualTouchDeviceConfig.java new file mode 100644 index 000000000000..2e2cfab0eee2 --- /dev/null +++ b/core/java/android/hardware/input/VirtualTouchDeviceConfig.java @@ -0,0 +1,103 @@ +/* + * Copyright 2023 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.input; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.os.Parcel; + +/** + * Configurations to create a virtual touch-based device. + * + * @hide + */ +abstract class VirtualTouchDeviceConfig extends VirtualInputDeviceConfig { + + /** The touch device width. */ + private final int mWidth; + /** The touch device height. */ + private final int mHeight; + + VirtualTouchDeviceConfig(@NonNull Builder<? extends Builder<?>> builder) { + super(builder); + mWidth = builder.mWidth; + mHeight = builder.mHeight; + } + + VirtualTouchDeviceConfig(@NonNull Parcel in) { + super(in); + mWidth = in.readInt(); + mHeight = in.readInt(); + } + + /** Returns the touch device width. */ + public int getWidth() { + return mWidth; + } + + /** Returns the touch device height. */ + public int getHeight() { + return mHeight; + } + + @Override + void writeToParcel(@NonNull Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mWidth); + dest.writeInt(mHeight); + } + + @Override + @NonNull + String additionalFieldsToString() { + return " width=" + mWidth + " height=" + mHeight; + } + + /** + * Builder for creating a {@link VirtualTouchDeviceConfig}. + * + * @param <T> The subclass to be built. + */ + abstract static class Builder<T extends Builder<T>> + extends VirtualInputDeviceConfig.Builder<T> { + + private final int mWidth; + private final int mHeight; + + /** + * Creates a new instance for the given dimensions of the touch device. + * + * <p>The dimensions are not pixels but in the screen's raw coordinate space. They do + * not necessarily have to correspond to the display size or aspect ratio. In this case the + * framework will handle the scaling appropriately. + * + * @param touchDeviceWidth The width of the touch device. + * @param touchDeviceHeight The height of the touch device. + */ + Builder(@IntRange(from = 1) int touchDeviceWidth, + @IntRange(from = 1) int touchDeviceHeight) { + if (touchDeviceHeight <= 0 || touchDeviceWidth <= 0) { + throw new IllegalArgumentException( + "Cannot create a virtual touch-based device, dimensions must be " + + "positive. Got: (" + touchDeviceHeight + ", " + + touchDeviceWidth + ")"); + } + mHeight = touchDeviceHeight; + mWidth = touchDeviceWidth; + } + } +} diff --git a/core/java/android/hardware/input/VirtualTouchscreenConfig.java b/core/java/android/hardware/input/VirtualTouchscreenConfig.java index 63084592a2e8..851cee6e51d2 100644 --- a/core/java/android/hardware/input/VirtualTouchscreenConfig.java +++ b/core/java/android/hardware/input/VirtualTouchscreenConfig.java @@ -23,38 +23,19 @@ import android.os.Parcel; import android.os.Parcelable; /** - * Configurations to create virtual touchscreen. + * Configurations to create a virtual touchscreen. * * @hide */ @SystemApi -public final class VirtualTouchscreenConfig extends VirtualInputDeviceConfig implements Parcelable { - - /** The touchscreen width. */ - private final int mWidth; - /** The touchscreen height. */ - private final int mHeight; +public final class VirtualTouchscreenConfig extends VirtualTouchDeviceConfig implements Parcelable { private VirtualTouchscreenConfig(@NonNull Builder builder) { super(builder); - mWidth = builder.mWidth; - mHeight = builder.mHeight; } private VirtualTouchscreenConfig(@NonNull Parcel in) { super(in); - mWidth = in.readInt(); - mHeight = in.readInt(); - } - - /** Returns the touchscreen width. */ - public int getWidth() { - return mWidth; - } - - /** Returns the touchscreen height. */ - public int getHeight() { - return mHeight; } @Override @@ -65,19 +46,11 @@ public final class VirtualTouchscreenConfig extends VirtualInputDeviceConfig imp @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeInt(mWidth); - dest.writeInt(mHeight); - } - - @Override - @NonNull - String additionalFieldsToString() { - return " width=" + mWidth + " height=" + mHeight; } @NonNull public static final Creator<VirtualTouchscreenConfig> CREATOR = - new Creator<VirtualTouchscreenConfig>() { + new Creator<>() { @Override public VirtualTouchscreenConfig createFromParcel(Parcel in) { return new VirtualTouchscreenConfig(in); @@ -92,9 +65,7 @@ public final class VirtualTouchscreenConfig extends VirtualInputDeviceConfig imp /** * Builder for creating a {@link VirtualTouchscreenConfig}. */ - public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> { - private int mWidth; - private int mHeight; + public static final class Builder extends VirtualTouchDeviceConfig.Builder<Builder> { /** * Creates a new instance for the given dimensions of the {@link VirtualTouchscreen}. @@ -108,14 +79,7 @@ public final class VirtualTouchscreenConfig extends VirtualInputDeviceConfig imp */ public Builder(@IntRange(from = 1) int touchscreenWidth, @IntRange(from = 1) int touchscreenHeight) { - if (touchscreenHeight <= 0 || touchscreenWidth <= 0) { - throw new IllegalArgumentException( - "Cannot create a virtual touchscreen, touchscreen dimensions must be " - + "positive. Got: (" + touchscreenHeight + ", " - + touchscreenWidth + ")"); - } - mHeight = touchscreenHeight; - mWidth = touchscreenWidth; + super(touchscreenWidth, touchscreenHeight); } /** diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java index 8510084c309d..f2ef185a0500 100644 --- a/core/java/android/os/BugreportParams.java +++ b/core/java/android/os/BugreportParams.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.admin.flags.Flags; +import android.compat.annotation.UnsupportedAppUsage; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -131,6 +132,7 @@ public final class BugreportParams { */ @TestApi @FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED) + @UnsupportedAppUsage public static final int BUGREPORT_MODE_ONBOARDING = IDumpstate.BUGREPORT_MODE_ONBOARDING; /** diff --git a/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java b/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java new file mode 100644 index 000000000000..819cc8c7beef --- /dev/null +++ b/core/java/android/tracing/perfetto/CreateIncrementalStateArgs.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 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.tracing.perfetto; + +import android.annotation.Nullable; + +/** + * @hide + * @param <DataSourceInstanceType> The type of datasource instance this state applied to. + */ +public class CreateIncrementalStateArgs<DataSourceInstanceType extends DataSourceInstance> { + private final DataSource<DataSourceInstanceType, Object, Object> mDataSource; + private final int mInstanceIndex; + + CreateIncrementalStateArgs(DataSource dataSource, int instanceIndex) { + this.mDataSource = dataSource; + this.mInstanceIndex = instanceIndex; + } + + /** + * Gets the datasource instance for this state with a lock. + * releaseDataSourceInstanceLocked must be called before this can be called again. + * @return The data source instance for this state. + * Null if the datasource instance no longer exists. + */ + public @Nullable DataSourceInstanceType getDataSourceInstanceLocked() { + return mDataSource.getDataSourceInstanceLocked(mInstanceIndex); + } +} diff --git a/core/java/android/tracing/perfetto/CreateTlsStateArgs.java b/core/java/android/tracing/perfetto/CreateTlsStateArgs.java new file mode 100644 index 000000000000..3fad2d1b3e53 --- /dev/null +++ b/core/java/android/tracing/perfetto/CreateTlsStateArgs.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 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.tracing.perfetto; + +import android.annotation.Nullable; + +/** + * @hide + * @param <DataSourceInstanceType> The type of datasource instance this state applied to. + */ +public class CreateTlsStateArgs<DataSourceInstanceType extends DataSourceInstance> { + private final DataSource<DataSourceInstanceType, Object, Object> mDataSource; + private final int mInstanceIndex; + + CreateTlsStateArgs(DataSource dataSource, int instanceIndex) { + this.mDataSource = dataSource; + this.mInstanceIndex = instanceIndex; + } + + /** + * Gets the datasource instance for this state with a lock. + * releaseDataSourceInstanceLocked must be called before this can be called again. + * @return The data source instance for this state. + * Null if the datasource instance no longer exists. + */ + public @Nullable DataSourceInstanceType getDataSourceInstanceLocked() { + return mDataSource.getDataSourceInstanceLocked(mInstanceIndex); + } +} diff --git a/core/java/android/tracing/perfetto/DataSource.java b/core/java/android/tracing/perfetto/DataSource.java new file mode 100644 index 000000000000..4e08aeef88e6 --- /dev/null +++ b/core/java/android/tracing/perfetto/DataSource.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2023 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.tracing.perfetto; + +import android.util.proto.ProtoInputStream; + +/** + * Templated base class meant to be derived by embedders to create a custom data + * source. + * + * @param <DataSourceInstanceType> The type for the DataSource instances that will be created from + * this DataSource type. + * @param <TlsStateType> The type of the custom TLS state, if any is used. + * @param <IncrementalStateType> The type of the custom incremental state, if any is used. + * + * @hide + */ +public abstract class DataSource<DataSourceInstanceType extends DataSourceInstance, + TlsStateType, IncrementalStateType> { + protected final long mNativeObj; + + public final String name; + + /** + * A function implemented by each datasource to create a new data source instance. + * + * @param configStream A ProtoInputStream to read the tracing instance's config. + * @return A new data source instance setup with the provided config. + */ + public abstract DataSourceInstanceType createInstance( + ProtoInputStream configStream, int instanceIndex); + + /** + * Constructor for datasource base class. + * + * @param name The fully qualified name of the datasource. + */ + public DataSource(String name) { + this.name = name; + this.mNativeObj = nativeCreate(this, name); + } + + /** + * The main tracing method. Tracing code should call this passing a lambda as + * argument, with the following signature: void(TraceContext). + * <p> + * The lambda will be called synchronously (i.e., always before trace() + * returns) only if tracing is enabled and the data source has been enabled in + * the tracing config. + * <p> + * The lambda can be called more than once per trace() call, in the case of + * concurrent tracing sessions (or even if the data source is instantiated + * twice within the same trace config). + * + * @param fun The tracing lambda that will be called with the tracing contexts of each active + * tracing instance. + */ + public final void trace( + TraceFunction<DataSourceInstanceType, TlsStateType, IncrementalStateType> fun) { + nativeTrace(mNativeObj, fun); + } + + /** + * Flush any trace data from this datasource that has not yet been flushed. + */ + public final void flush() { + nativeFlushAll(mNativeObj); + } + + /** + * Override this method to create a custom TlsState object for your DataSource. A new instance + * will be created per trace instance per thread. + * + * NOTE: Should only be called from native side. + */ + protected TlsStateType createTlsState(CreateTlsStateArgs<DataSourceInstanceType> args) { + return null; + } + + /** + * Override this method to create and use a custom IncrementalState object for your DataSource. + * + * NOTE: Should only be called from native side. + */ + protected IncrementalStateType createIncrementalState( + CreateIncrementalStateArgs<DataSourceInstanceType> args) { + return null; + } + + /** + * Registers the data source on all tracing backends, including ones that + * connect after the registration. Doing so enables the data source to receive + * Setup/Start/Stop notifications and makes the trace() method work when + * tracing is enabled and the data source is selected. + * <p> + * NOTE: Once registered, we cannot unregister the data source. Therefore, we should avoid + * creating and registering data source where not strictly required. This is a fundamental + * limitation of Perfetto itself. + * + * @param params Params to initialize the datasource with. + */ + public void register(DataSourceParams params) { + nativeRegisterDataSource(this.mNativeObj, params.bufferExhaustedPolicy); + } + + /** + * Gets the datasource instance with a specified index. + * IMPORTANT: releaseDataSourceInstance must be called after using the datasource instance. + * @param instanceIndex The index of the datasource to lock and get. + * @return The DataSourceInstance at index instanceIndex. + * Null if the datasource instance at the requested index doesn't exist. + */ + public DataSourceInstanceType getDataSourceInstanceLocked(int instanceIndex) { + return (DataSourceInstanceType) nativeGetPerfettoInstanceLocked(mNativeObj, instanceIndex); + } + + /** + * Unlock the datasource at the specified index. + * @param instanceIndex The index of the datasource to unlock. + */ + protected void releaseDataSourceInstance(int instanceIndex) { + nativeReleasePerfettoInstanceLocked(mNativeObj, instanceIndex); + } + + /** + * Called from native side when a new tracing instance starts. + * + * @param rawConfig byte array of the PerfettoConfig encoded proto. + * @return A new Java DataSourceInstance object. + */ + private DataSourceInstanceType createInstance(byte[] rawConfig, int instanceIndex) { + final ProtoInputStream inputStream = new ProtoInputStream(rawConfig); + return this.createInstance(inputStream, instanceIndex); + } + + private static native void nativeRegisterDataSource( + long dataSourcePtr, int bufferExhaustedPolicy); + + private static native long nativeCreate(DataSource thiz, String name); + private static native void nativeTrace( + long nativeDataSourcePointer, TraceFunction traceFunction); + private static native void nativeFlushAll(long nativeDataSourcePointer); + private static native long nativeGetFinalizer(); + + private static native DataSourceInstance nativeGetPerfettoInstanceLocked( + long dataSourcePtr, int dsInstanceIdx); + private static native void nativeReleasePerfettoInstanceLocked( + long dataSourcePtr, int dsInstanceIdx); +} diff --git a/core/java/android/tracing/perfetto/DataSourceInstance.java b/core/java/android/tracing/perfetto/DataSourceInstance.java new file mode 100644 index 000000000000..49945013ae87 --- /dev/null +++ b/core/java/android/tracing/perfetto/DataSourceInstance.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 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.tracing.perfetto; + +/** + * @hide + */ +public abstract class DataSourceInstance implements AutoCloseable { + private final DataSource mDataSource; + private final int mInstanceIndex; + + public DataSourceInstance(DataSource dataSource, int instanceIndex) { + this.mDataSource = dataSource; + this.mInstanceIndex = instanceIndex; + } + + /** + * Executed when the tracing instance starts running. + * <p> + * NOTE: This callback executes on the Perfetto internal thread and is blocking. + * Anything that is run in this callback should execute quickly. + * + * @param args Start arguments. + */ + protected void onStart(StartCallbackArguments args) {} + + /** + * Executed when a flush is triggered. + * <p> + * NOTE: This callback executes on the Perfetto internal thread and is blocking. + * Anything that is run in this callback should execute quickly. + * @param args Flush arguments. + */ + protected void onFlush(FlushCallbackArguments args) {} + + /** + * Executed when the tracing instance is stopped. + * <p> + * NOTE: This callback executes on the Perfetto internal thread and is blocking. + * Anything that is run in this callback should execute quickly. + * @param args Stop arguments. + */ + protected void onStop(StopCallbackArguments args) {} + + @Override + public final void close() { + this.release(); + } + + /** + * Release the lock on the datasource once you are finished using it. + * Only required to be called when instance was retrieved with + * `DataSource#getDataSourceInstanceLocked`. + */ + public final void release() { + mDataSource.releaseDataSourceInstance(mInstanceIndex); + } +} diff --git a/core/java/android/tracing/perfetto/DataSourceParams.java b/core/java/android/tracing/perfetto/DataSourceParams.java new file mode 100644 index 000000000000..6cd04e3d9a8b --- /dev/null +++ b/core/java/android/tracing/perfetto/DataSourceParams.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 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.tracing.perfetto; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * DataSource Parameters + * + * @hide + */ +public class DataSourceParams { + /** + * @hide + */ + @IntDef(value = { + PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP, + PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PerfettoDsBufferExhausted {} + + // If the data source runs out of space when trying to acquire a new chunk, + // it will drop data. + public static final int PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP = 0; + + // If the data source runs out of space when trying to acquire a new chunk, + // it will stall, retry and eventually abort if a free chunk is not acquired + // after a while. + public static final int PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT = 1; + + public static DataSourceParams DEFAULTS = + new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP); + + public DataSourceParams(@PerfettoDsBufferExhausted int bufferExhaustedPolicy) { + this.bufferExhaustedPolicy = bufferExhaustedPolicy; + } + + public final @PerfettoDsBufferExhausted int bufferExhaustedPolicy; +} diff --git a/core/java/android/tracing/perfetto/FlushCallbackArguments.java b/core/java/android/tracing/perfetto/FlushCallbackArguments.java new file mode 100644 index 000000000000..ecf6aee9ef50 --- /dev/null +++ b/core/java/android/tracing/perfetto/FlushCallbackArguments.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 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.tracing.perfetto; + +/** + * @hide + */ +public class FlushCallbackArguments { +} diff --git a/core/java/android/tracing/perfetto/InitArguments.java b/core/java/android/tracing/perfetto/InitArguments.java new file mode 100644 index 000000000000..da8c273fd14e --- /dev/null +++ b/core/java/android/tracing/perfetto/InitArguments.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 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.tracing.perfetto; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @hide + */ +public class InitArguments { + public final @PerfettoBackend int backends; + + /** + * @hide + */ + @IntDef(value = { + PERFETTO_BACKEND_IN_PROCESS, + PERFETTO_BACKEND_SYSTEM, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PerfettoBackend {} + + // The in-process tracing backend. Keeps trace buffers in the process memory. + public static final int PERFETTO_BACKEND_IN_PROCESS = (1 << 0); + + // The system tracing backend. Connects to the system tracing service (e.g. + // on Linux/Android/Mac uses a named UNIX socket). + public static final int PERFETTO_BACKEND_SYSTEM = (1 << 1); + + public static InitArguments DEFAULTS = new InitArguments(PERFETTO_BACKEND_SYSTEM); + + public static InitArguments TESTING = new InitArguments(PERFETTO_BACKEND_IN_PROCESS); + + public InitArguments(@PerfettoBackend int backends) { + this.backends = backends; + } +} diff --git a/core/java/android/tracing/perfetto/Producer.java b/core/java/android/tracing/perfetto/Producer.java new file mode 100644 index 000000000000..a1b3eb754157 --- /dev/null +++ b/core/java/android/tracing/perfetto/Producer.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 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.tracing.perfetto; + +/** + * @hide + */ +public class Producer { + + /** + * Initializes the global Perfetto producer. + * + * @param args arguments on how to initialize the Perfetto producer. + */ + public static void init(InitArguments args) { + nativePerfettoProducerInit(args.backends); + } + + private static native void nativePerfettoProducerInit(int backends); +} diff --git a/core/java/android/tracing/perfetto/StartCallbackArguments.java b/core/java/android/tracing/perfetto/StartCallbackArguments.java new file mode 100644 index 000000000000..9739d271a13f --- /dev/null +++ b/core/java/android/tracing/perfetto/StartCallbackArguments.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 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.tracing.perfetto; + +/** + * @hide + */ +public class StartCallbackArguments { +} diff --git a/core/java/android/tracing/perfetto/StopCallbackArguments.java b/core/java/android/tracing/perfetto/StopCallbackArguments.java new file mode 100644 index 000000000000..0cd1a188fa0c --- /dev/null +++ b/core/java/android/tracing/perfetto/StopCallbackArguments.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 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.tracing.perfetto; + +/** + * @hide + */ +public class StopCallbackArguments { +} diff --git a/core/java/android/tracing/perfetto/TraceFunction.java b/core/java/android/tracing/perfetto/TraceFunction.java new file mode 100644 index 000000000000..62941df70a48 --- /dev/null +++ b/core/java/android/tracing/perfetto/TraceFunction.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 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.tracing.perfetto; + +import java.io.IOException; + +/** + * The interface for the trace function called from native on a trace call with a context. + * + * @param <DataSourceInstanceType> The type of DataSource this tracing context is for. + * @param <TlsStateType> The type of the custom TLS state, if any is used. + * @param <IncrementalStateType> The type of the custom incremental state, if any is used. + * + * @hide + */ +public interface TraceFunction<DataSourceInstanceType extends DataSourceInstance, + TlsStateType, IncrementalStateType> { + + /** + * This function will be called synchronously (i.e., always before trace() returns) only if + * tracing is enabled and the data source has been enabled in the tracing config. + * It can be called more than once per trace() call, in the case of concurrent tracing sessions + * (or even if the data source is instantiated twice within the same trace config). + * + * @param ctx the tracing context to trace for in the trace function. + */ + void trace(TracingContext<DataSourceInstanceType, TlsStateType, IncrementalStateType> ctx) + throws IOException; +} diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java new file mode 100644 index 000000000000..0bce26e007a1 --- /dev/null +++ b/core/java/android/tracing/perfetto/TracingContext.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2023 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.tracing.perfetto; + +import android.util.proto.ProtoOutputStream; + +import java.util.ArrayList; +import java.util.List; + +/** + * Argument passed to the lambda function passed to Trace(). + * + * @param <DataSourceInstanceType> The type of the datasource this tracing context is for. + * @param <TlsStateType> The type of the custom TLS state, if any is used. + * @param <IncrementalStateType> The type of the custom incremental state, if any is used. + * + * @hide + */ +public class TracingContext<DataSourceInstanceType extends DataSourceInstance, + TlsStateType, IncrementalStateType> { + + private final long mContextPtr; + private final TlsStateType mTlsState; + private final IncrementalStateType mIncrementalState; + private final List<ProtoOutputStream> mTracePackets = new ArrayList<>(); + + // Should only be created from the native side. + private TracingContext(long contextPtr, TlsStateType tlsState, + IncrementalStateType incrementalState) { + this.mContextPtr = contextPtr; + this.mTlsState = tlsState; + this.mIncrementalState = incrementalState; + } + + /** + * Creates a new output stream to be used to write a trace packet to. The output stream will be + * encoded to the proto binary representation when the callback trace function finishes and + * send over to the native side to be included in the proto buffer. + * + * @return A proto output stream to write a trace packet proto to + */ + public ProtoOutputStream newTracePacket() { + final ProtoOutputStream os = new ProtoOutputStream(0); + mTracePackets.add(os); + return os; + } + + /** + * Forces a commit of the thread-local tracing data written so far to the + * service. This is almost never required (tracing data is periodically + * committed as trace pages are filled up) and has a non-negligible + * performance hit (requires an IPC + refresh of the current thread-local + * chunk). The only case when this should be used is when handling OnStop() + * asynchronously, to ensure sure that the data is committed before the + * Stop timeout expires. + */ + public void flush() { + nativeFlush(this, mContextPtr); + } + + /** + * Can optionally be used to store custom per-sequence + * session data, which is not reset when incremental state is cleared + * (e.g. configuration options). + * + * @return The TlsState instance for the tracing thread and instance. + */ + public TlsStateType getCustomTlsState() { + return this.mTlsState; + } + + /** + * Can optionally be used store custom per-sequence + * incremental data (e.g., interning tables). + * + * @return The current IncrementalState object instance. + */ + public IncrementalStateType getIncrementalState() { + return this.mIncrementalState; + } + + // Called from native to get trace packets + private byte[][] getAndClearAllPendingTracePackets() { + byte[][] res = new byte[mTracePackets.size()][]; + for (int i = 0; i < mTracePackets.size(); i++) { + ProtoOutputStream tracePacket = mTracePackets.get(i); + res[i] = tracePacket.getBytes(); + } + + mTracePackets.clear(); + return res; + } + + // private static native void nativeFlush(long nativeDataSourcePointer); + private static native void nativeFlush(TracingContext thiz, long ctxPointer); +} diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java index 828ec265f4c8..c6271d27cb37 100644 --- a/core/java/android/webkit/URLUtil.java +++ b/core/java/android/webkit/URLUtil.java @@ -16,20 +16,40 @@ package android.webkit; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.compat.Compatibility; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.net.ParseException; import android.net.Uri; import android.net.WebAddress; +import android.os.Build; import android.util.Log; import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.Charset; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; public final class URLUtil { + /** + * This feature enables parsing of Content-Disposition headers that conform to RFC 6266. In + * particular, this enables parsing of {@code filename*} values which can use a different + * character encoding. + * + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + @FlaggedApi(android.os.Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM) + public static final long PARSE_CONTENT_DISPOSITION_USING_RFC_6266 = 319400769L; + private static final String LOGTAG = "webkit"; private static final boolean TRACE = false; @@ -293,21 +313,58 @@ public final class URLUtil { /** * Guesses canonical filename that a download would have, using the URL and contentDisposition. - * File extension, if not defined, is added based on the mimetype + * + * <p>File extension, if not defined, is added based on the mimetype. + * + * <p>The {@code contentDisposition} argument will be treated differently depending on + * targetSdkVersion. + * + * <ul> + * <li>For targetSDK versions < {@code VANILLA_ICE_CREAM} it will be parsed based on RFC + * 2616. + * <li>For targetSDK versions >= {@code VANILLA_ICE_CREAM} it will be parsed based on RFC + * 6266. + * </ul> + * + * In practice, this means that from {@code VANILLA_ICE_CREAM}, this method will be able to + * parse {@code filename*} directives in the {@code contentDisposition} string. + * + * <p>The function also changed in the following ways in {@code VANILLA_ICE_CREAM}: + * + * <ul> + * <li>If the suggested file type extension doesn't match the passed {@code mimeType}, the + * method will append the appropriate extension instead of replacing the current + * extension. + * <li>If the suggested file name contains a path separator ({@code "/"}), the method will + * replace this with the underscore character ({@code "_"}) instead of splitting the + * result and only using the last part. + * </ul> * * @param url Url to the content * @param contentDisposition Content-Disposition HTTP header or {@code null} * @param mimeType Mime-type of the content or {@code null} * @return suggested filename */ - public static final String guessFileName( + public static String guessFileName( + String url, @Nullable String contentDisposition, @Nullable String mimeType) { + if (android.os.Flags.androidOsBuildVanillaIceCream()) { + if (Compatibility.isChangeEnabled(PARSE_CONTENT_DISPOSITION_USING_RFC_6266)) { + return guessFileNameRfc6266(url, contentDisposition, mimeType); + } + } + + return guessFileNameRfc2616(url, contentDisposition, mimeType); + } + + /** Legacy implementation of guessFileName, based on RFC 2616. */ + private static String guessFileNameRfc2616( String url, @Nullable String contentDisposition, @Nullable String mimeType) { String filename = null; String extension = null; // If we couldn't do anything with the hint, move toward the content disposition if (contentDisposition != null) { - filename = parseContentDisposition(contentDisposition); + filename = parseContentDispositionRfc2616(contentDisposition); if (filename != null) { int index = filename.lastIndexOf('/') + 1; if (index > 0) { @@ -384,6 +441,128 @@ public final class URLUtil { return filename + extension; } + /** + * Guesses canonical filename that a download would have, using the URL and contentDisposition. + * Uses RFC 6266 for parsing the contentDisposition header value. + */ + @NonNull + private static String guessFileNameRfc6266( + @NonNull String url, @Nullable String contentDisposition, @Nullable String mimeType) { + String filename = getFilenameSuggestion(url, contentDisposition); + // Split filename between base and extension + // Add an extension if filename does not have one + String extensionFromMimeType = suggestExtensionFromMimeType(mimeType); + + if (filename.indexOf('.') < 0) { + // Filename does not have an extension, use the suggested one. + return filename + extensionFromMimeType; + } + + // Filename already contains at least one dot. + // Compare the last segment of the extension against the mime type. + // If there's a mismatch, add the suggested extension instead. + if (mimeType != null && extensionDifferentFromMimeType(filename, mimeType)) { + return filename + extensionFromMimeType; + } + return filename; + } + + /** + * Get the suggested file name from the {@code contentDisposition} or {@code url}. Will ensure + * that the filename contains no path separators by replacing them with the {@code "_"} + * character. + */ + @NonNull + private static String getFilenameSuggestion(String url, @Nullable String contentDisposition) { + // First attempt to parse the Content-Disposition header if available + if (contentDisposition != null) { + String filename = getFilenameFromContentDispositionRfc6266(contentDisposition); + if (filename != null) { + return replacePathSeparators(filename); + } + } + + // Try to generate a filename based on the URL. + if (url != null) { + Uri parsedUri = Uri.parse(url); + String lastPathSegment = parsedUri.getLastPathSegment(); + if (lastPathSegment != null) { + return replacePathSeparators(lastPathSegment); + } + } + + // Finally, if couldn't get filename from URI, get a generic filename. + return "downloadfile"; + } + + /** + * Replace all instances of {@code "/"} with {@code "_"} to avoid filenames that navigate the + * path. + */ + @NonNull + private static String replacePathSeparators(@NonNull String raw) { + return raw.replaceAll("/", "_"); + } + + /** + * Check if the {@code filename} has an extension that is different from the expected one based + * on the {@code mimeType}. + */ + private static boolean extensionDifferentFromMimeType( + @NonNull String filename, @NonNull String mimeType) { + int lastDotIndex = filename.lastIndexOf('.'); + String typeFromExt = + MimeTypeMap.getSingleton() + .getMimeTypeFromExtension(filename.substring(lastDotIndex + 1)); + return typeFromExt != null && !typeFromExt.equalsIgnoreCase(mimeType); + } + + /** + * Get a candidate file extension (including the {@code .}) for the given mimeType. will return + * {@code ".bin"} if {@code mimeType} is {@code null} + * + * @param mimeType Reported mimetype + * @return A file extension, including the {@code .} + */ + @NonNull + private static String suggestExtensionFromMimeType(@Nullable String mimeType) { + if (mimeType == null) { + return ".bin"; + } + String extensionFromMimeType = + MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType); + if (extensionFromMimeType != null) { + return "." + extensionFromMimeType; + } + if (mimeType.equalsIgnoreCase("text/html")) { + return ".html"; + } else if (mimeType.toLowerCase(Locale.ROOT).startsWith("text/")) { + return ".txt"; + } else { + return ".bin"; + } + } + + /** + * Parse the Content-Disposition HTTP Header. + * + * <p>Behavior depends on targetSdkVersion. + * + * <ul> + * <li>For targetSDK versions < {@code VANILLA_ICE_CREAM} it will parse based on RFC 2616. + * <li>For targetSDK versions >= {@code VANILLA_ICE_CREAM} it will parse based on RFC 6266. + * </ul> + */ + @UnsupportedAppUsage + static String parseContentDisposition(String contentDisposition) { + if (android.os.Flags.androidOsBuildVanillaIceCream()) { + if (Compatibility.isChangeEnabled(PARSE_CONTENT_DISPOSITION_USING_RFC_6266)) { + return getFilenameFromContentDispositionRfc6266(contentDisposition); + } + } + return parseContentDispositionRfc2616(contentDisposition); + } + /** Regex used to parse content-disposition headers */ private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern.compile( @@ -391,15 +570,14 @@ public final class URLUtil { Pattern.CASE_INSENSITIVE); /** - * Parse the Content-Disposition HTTP Header. The format of the header is defined here: - * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for - * content that is going to be downloaded to the file system. We only support the attachment - * type. Note that RFC 2616 specifies the filename value must be double-quoted. Unfortunately - * some servers do not quote the value so to maintain consistent behaviour with other browsers, - * we allow unquoted values too. + * Parse the Content-Disposition HTTP Header. The format of the header is defined here: <a + * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html">rfc2616 Section 19</a>. This + * header provides a filename for content that is going to be downloaded to the file system. We + * only support the attachment type. Note that RFC 2616 specifies the filename value must be + * double-quoted. Unfortunately some servers do not quote the value so to maintain consistent + * behaviour with other browsers, we allow unquoted values too. */ - @UnsupportedAppUsage - static String parseContentDisposition(String contentDisposition) { + private static String parseContentDispositionRfc2616(String contentDisposition) { try { Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); if (m.find()) { @@ -410,4 +588,136 @@ public final class URLUtil { } return null; } + + /** + * Pattern for parsing individual content disposition key-value pairs. + * + * <p>The pattern will attempt to parse the value as either single-, double-, or unquoted. For + * the single- and double-quoted options, the pattern allows escaped quotes as part of the + * value, as per <a href="https://datatracker.ietf.org/doc/html/rfc2616#section-2.2">rfc2616 + * section-2.2</a> + */ + @SuppressWarnings("RegExpRepeatedSpace") // Spaces are only for readability. + private static final Pattern DISPOSITION_PATTERN = + Pattern.compile( + """ + \\s*(\\S+?) # Group 1: parameter name + \\s*=\\s* # Match equals sign + (?: # non-capturing group of options + '( (?: [^'\\\\] | \\\\. )* )' # Group 2: single-quoted + | "( (?: [^"\\\\] | \\\\. )* )" # Group 3: double-quoted + | ( [^'"][^;\\s]* ) # Group 4: un-quoted parameter + )\\s*;? # Optional end semicolon""", + Pattern.COMMENTS); + + /** + * Extract filename from a {@code Content-Disposition} header value. + * + * <p>This method implements the parsing defined in <a + * href="https://datatracker.ietf.org/doc/html/rfc6266">RFC 6266</a>, supporting both the {@code + * filename} and {@code filename*} disposition parameters. If the passed header value has the + * {@code "inline"} disposition type, this method will return {@code null} to indicate that a + * download was not intended. + * + * <p>If both {@code filename*} and {@code filename} is present, the former will be returned, as + * per the RFC. Invalid encoded values will be ignored. + * + * @param contentDisposition Value of {@code Content-Disposition} header. + * @return The filename suggested by the header or {@code null} if no filename could be parsed + * from the header value. + */ + @Nullable + private static String getFilenameFromContentDispositionRfc6266( + @NonNull String contentDisposition) { + String[] parts = contentDisposition.trim().split(";", 2); + if (parts.length < 2) { + // Need at least 2 parts, the `disposition-type` and at least one `disposition-parm`. + return null; + } + String dispositionType = parts[0].trim(); + if ("inline".equalsIgnoreCase(dispositionType)) { + // "inline" should not result in a download. + // Unknown disposition types should be handles as "attachment" + // https://datatracker.ietf.org/doc/html/rfc6266#section-4.2 + return null; + } + String dispositionParameters = parts[1]; + Matcher matcher = DISPOSITION_PATTERN.matcher(dispositionParameters); + String filename = null; + String filenameExt = null; + while (matcher.find()) { + String parameter = matcher.group(1); + String value; + if (matcher.group(2) != null) { + value = removeSlashEscapes(matcher.group(2)); // Value was single-quoted + } else if (matcher.group(3) != null) { + value = removeSlashEscapes(matcher.group(3)); // Value was double-quoted + } else { + value = matcher.group(4); // Value was un-quoted + } + + if (parameter == null || value == null) { + continue; + } + + if ("filename*".equalsIgnoreCase(parameter)) { + filenameExt = parseExtValueString(value); + } else if ("filename".equalsIgnoreCase(parameter)) { + filename = value; + } + } + + // RFC 6266 dictates the filenameExt should be preferred if present. + if (filenameExt != null) { + return filenameExt; + } + return filename; + } + + /** Replace escapes of the \X form with X. */ + private static String removeSlashEscapes(String raw) { + if (raw == null) { + return null; + } + return raw.replaceAll("\\\\(.)", "$1"); + } + + /** + * Parse an extended value string which can be percent-encoded. Return {@code} null if unable to + * parse the string. + */ + private static String parseExtValueString(String raw) { + String[] parts = raw.split("'", 3); + if (parts.length < 3) { + return null; + } + + String encoding = parts[0]; + // Intentionally ignore parts[1] (language). + String valueChars = parts[2]; + + try { + // The URLDecoder force-decodes + as " " + // so preemptively replace all values with the encoded value to preserve them. + Charset charset = Charset.forName(encoding); + String valueWithEncodedPlus = encodePlusCharacters(valueChars, charset); + return URLDecoder.decode(valueWithEncodedPlus, charset); + } catch (RuntimeException ignored) { + return null; // Ignoring an un-parsable value is within spec. + } + } + + /** + * Replace all instances of {@code "+"} with the percent-encoded equivalent for the given {@code + * charset}. + */ + @NonNull + private static String encodePlusCharacters(@NonNull String valueChars, Charset charset) { + StringBuilder sb = new StringBuilder(); + for (byte b : charset.encode("+").array()) { + // Formatting a byte is not possible with TextUtils.formatSimple + sb.append(String.format("%02x", b)); + } + return valueChars.replaceAll("\\+", sb.toString()); + } } diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index 0ef37d14420c..53b047a17f6d 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -16,6 +16,8 @@ package android.webkit; +import static android.webkit.Flags.updateServiceV2; + import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.UptimeMillisLong; @@ -33,6 +35,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.Trace; +import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.ArraySet; import android.util.Log; @@ -410,6 +413,21 @@ public final class WebViewFactory { } } + // Returns whether the given package is enabled. + // This state can be changed by the user from Settings->Apps + private static boolean isEnabledPackage(PackageInfo packageInfo) { + if (packageInfo == null) return false; + return packageInfo.applicationInfo.enabled; + } + + // Return {@code true} if the package is installed and not hidden + private static boolean isInstalledPackage(PackageInfo packageInfo) { + if (packageInfo == null) return false; + return (((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0) + && ((packageInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HIDDEN) + == 0)); + } + @UnsupportedAppUsage private static Context getWebViewContextAndSetProvider() throws MissingWebViewPackageException { Application initialApplication = AppGlobals.getInitialApplication(); @@ -456,6 +474,21 @@ public final class WebViewFactory { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); } + if (updateServiceV2() && !isInstalledPackage(newPackageInfo)) { + throw new MissingWebViewPackageException( + TextUtils.formatSimple( + "Current WebView Package (%s) is not installed for the current " + + "user", + newPackageInfo.packageName)); + } + + if (updateServiceV2() && !isEnabledPackage(newPackageInfo)) { + throw new MissingWebViewPackageException( + TextUtils.formatSimple( + "Current WebView Package (%s) is not enabled for the current user", + newPackageInfo.packageName)); + } + // Validate the newly fetched package info, throws MissingWebViewPackageException on // failure verifyPackageInfo(response.packageInfo, newPackageInfo); diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 2a744e343ccd..c8fd246a255b 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -269,6 +269,9 @@ cc_library_shared_for_libandroid_runtime { "android_window_WindowInfosListener.cpp", "android_window_ScreenCapture.cpp", "jni_common.cpp", + "android_tracing_PerfettoDataSource.cpp", + "android_tracing_PerfettoDataSourceInstance.cpp", + "android_tracing_PerfettoProducer.cpp", ], static_libs: [ @@ -282,6 +285,7 @@ cc_library_shared_for_libandroid_runtime { "libscrypt_static", "libstatssocket_lazy", "libskia", + "libperfetto_client_experimental", ], shared_libs: [ @@ -355,6 +359,7 @@ cc_library_shared_for_libandroid_runtime { "server_configurable_flags", "libimage_io", "libultrahdr", + "libperfetto_c", ], export_shared_lib_headers: [ // our headers include libnativewindow's public headers diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 17aad43edb6b..7a16318f3276 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -220,6 +220,9 @@ extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); extern int register_android_window_WindowInfosListener(JNIEnv* env); extern int register_android_window_ScreenCapture(JNIEnv* env); extern int register_jni_common(JNIEnv* env); +extern int register_android_tracing_PerfettoDataSource(JNIEnv* env); +extern int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env); +extern int register_android_tracing_PerfettoProducer(JNIEnv* env); // Namespace for Android Runtime flags applied during boot time. static const char* RUNTIME_NATIVE_BOOT_NAMESPACE = "runtime_native_boot"; @@ -1675,6 +1678,10 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_window_WindowInfosListener), REG_JNI(register_android_window_ScreenCapture), REG_JNI(register_jni_common), + + REG_JNI(register_android_tracing_PerfettoDataSource), + REG_JNI(register_android_tracing_PerfettoDataSourceInstance), + REG_JNI(register_android_tracing_PerfettoProducer), }; /* diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp new file mode 100644 index 000000000000..d71069866d89 --- /dev/null +++ b/core/jni/android_tracing_PerfettoDataSource.cpp @@ -0,0 +1,437 @@ +/* + * Copyright 2023 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 "Perfetto" + +#include "android_tracing_PerfettoDataSource.h" + +#include <android_runtime/AndroidRuntime.h> +#include <android_runtime/Log.h> +#include <nativehelper/JNIHelp.h> +#include <perfetto/public/data_source.h> +#include <perfetto/public/producer.h> +#include <perfetto/public/protos/trace/test_event.pzc.h> +#include <perfetto/public/protos/trace/trace_packet.pzc.h> +#include <perfetto/tracing.h> +#include <utils/Log.h> +#include <utils/RefBase.h> + +#include <sstream> +#include <thread> + +#include "core_jni_helpers.h" + +namespace android { + +static struct { + jclass clazz; + jmethodID createInstance; + jmethodID createTlsState; + jmethodID createIncrementalState; +} gPerfettoDataSourceClassInfo; + +static struct { + jclass clazz; + jmethodID init; + jmethodID getAndClearAllPendingTracePackets; +} gTracingContextClassInfo; + +static struct { + jclass clazz; + jmethodID init; +} gCreateTlsStateArgsClassInfo; + +static struct { + jclass clazz; + jmethodID init; +} gCreateIncrementalStateArgsClassInfo; + +static JavaVM* gVm; + +struct TlsState { + jobject jobj; +}; + +struct IncrementalState { + jobject jobj; +}; + +static void traceAllPendingPackets(JNIEnv* env, jobject jCtx, PerfettoDsTracerIterator* ctx) { + jobjectArray packets = + (jobjectArray)env + ->CallObjectMethod(jCtx, + gTracingContextClassInfo.getAndClearAllPendingTracePackets); + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + + LOG_ALWAYS_FATAL("Failed to call java context finalize method"); + } + + int packets_count = env->GetArrayLength(packets); + for (int i = 0; i < packets_count; i++) { + jbyteArray packet_proto_buffer = (jbyteArray)env->GetObjectArrayElement(packets, i); + + jbyte* raw_proto_buffer = env->GetByteArrayElements(packet_proto_buffer, 0); + int buffer_size = env->GetArrayLength(packet_proto_buffer); + + struct PerfettoDsRootTracePacket trace_packet; + PerfettoDsTracerPacketBegin(ctx, &trace_packet); + PerfettoPbMsgAppendBytes(&trace_packet.msg.msg, (const uint8_t*)raw_proto_buffer, + buffer_size); + PerfettoDsTracerPacketEnd(ctx, &trace_packet); + } +} + +PerfettoDataSource::PerfettoDataSource(JNIEnv* env, jobject javaDataSource, + std::string dataSourceName) + : dataSourceName(std::move(dataSourceName)), + mJavaDataSource(env->NewGlobalRef(javaDataSource)) {} + +jobject PerfettoDataSource::newInstance(JNIEnv* env, void* ds_config, size_t ds_config_size, + PerfettoDsInstanceIndex inst_id) { + jbyteArray configArray = env->NewByteArray(ds_config_size); + + void* temp = env->GetPrimitiveArrayCritical((jarray)configArray, 0); + memcpy(temp, ds_config, ds_config_size); + env->ReleasePrimitiveArrayCritical(configArray, temp, 0); + + jobject instance = + env->CallObjectMethod(mJavaDataSource, gPerfettoDataSourceClassInfo.createInstance, + configArray, inst_id); + + if (env->ExceptionCheck()) { + LOGE_EX(env); + env->ExceptionClear(); + LOG_ALWAYS_FATAL("Failed to create new Java Perfetto datasource instance"); + } + + return instance; +} + +jobject PerfettoDataSource::createTlsStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id) { + ScopedLocalRef<jobject> args(env, + env->NewObject(gCreateTlsStateArgsClassInfo.clazz, + gCreateTlsStateArgsClassInfo.init, mJavaDataSource, + inst_id)); + + ScopedLocalRef<jobject> tslState(env, + env->CallObjectMethod(mJavaDataSource, + gPerfettoDataSourceClassInfo + .createTlsState, + args.get())); + + if (env->ExceptionCheck()) { + LOGE_EX(env); + env->ExceptionClear(); + LOG_ALWAYS_FATAL("Failed to create new Java Perfetto incremental state"); + } + + return env->NewGlobalRef(tslState.get()); +} + +jobject PerfettoDataSource::createIncrementalStateGlobalRef(JNIEnv* env, + PerfettoDsInstanceIndex inst_id) { + ScopedLocalRef<jobject> args(env, + env->NewObject(gCreateIncrementalStateArgsClassInfo.clazz, + gCreateIncrementalStateArgsClassInfo.init, + mJavaDataSource, inst_id)); + + ScopedLocalRef<jobject> incrementalState(env, + env->CallObjectMethod(mJavaDataSource, + gPerfettoDataSourceClassInfo + .createIncrementalState, + args.get())); + + if (env->ExceptionCheck()) { + LOGE_EX(env); + env->ExceptionClear(); + LOG_ALWAYS_FATAL("Failed to create Java Perfetto incremental state"); + } + + return env->NewGlobalRef(incrementalState.get()); +} + +void PerfettoDataSource::trace(JNIEnv* env, jobject traceFunction) { + PERFETTO_DS_TRACE(dataSource, ctx) { + TlsState* tls_state = + reinterpret_cast<TlsState*>(PerfettoDsGetCustomTls(&dataSource, &ctx)); + IncrementalState* incr_state = reinterpret_cast<IncrementalState*>( + PerfettoDsGetIncrementalState(&dataSource, &ctx)); + + ScopedLocalRef<jobject> jCtx(env, + env->NewObject(gTracingContextClassInfo.clazz, + gTracingContextClassInfo.init, &ctx, + tls_state->jobj, incr_state->jobj)); + + jclass objclass = env->GetObjectClass(traceFunction); + jmethodID method = + env->GetMethodID(objclass, "trace", "(Landroid/tracing/perfetto/TracingContext;)V"); + if (method == 0) { + LOG_ALWAYS_FATAL("Failed to get method id"); + } + + env->ExceptionClear(); + + env->CallVoidMethod(traceFunction, method, jCtx.get()); + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + LOG_ALWAYS_FATAL("Failed to call java trace method"); + } + + traceAllPendingPackets(env, jCtx.get(), &ctx); + } +} + +void PerfettoDataSource::flushAll() { + PERFETTO_DS_TRACE(dataSource, ctx) { + PerfettoDsTracerFlush(&ctx, nullptr, nullptr); + } +} + +PerfettoDataSource::~PerfettoDataSource() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->DeleteWeakGlobalRef(mJavaDataSource); +} + +jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring name) { + const char* nativeString = env->GetStringUTFChars(name, 0); + PerfettoDataSource* dataSource = new PerfettoDataSource(env, javaDataSource, nativeString); + env->ReleaseStringUTFChars(name, nativeString); + + dataSource->incStrong((void*)nativeCreate); + + return reinterpret_cast<jlong>(dataSource); +} + +void nativeDestroy(void* ptr) { + PerfettoDataSource* dataSource = reinterpret_cast<PerfettoDataSource*>(ptr); + dataSource->decStrong((void*)nativeCreate); +} + +static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy)); +} + +void nativeTrace(JNIEnv* env, jclass clazz, jlong dataSourcePtr, jobject traceFunctionInterface) { + sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr); + + datasource->trace(env, traceFunctionInterface); +} + +void nativeFlush(JNIEnv* env, jclass clazz, jobject jCtx, jlong ctxPtr) { + auto* ctx = reinterpret_cast<struct PerfettoDsTracerIterator*>(ctxPtr); + traceAllPendingPackets(env, jCtx, ctx); + PerfettoDsTracerFlush(ctx, nullptr, nullptr); +} + +void nativeFlushAll(JNIEnv* env, jclass clazz, jlong ptr) { + sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ptr); + datasource->flushAll(); +} + +void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr, + int buffer_exhausted_policy) { + sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr); + + struct PerfettoDsParams params = PerfettoDsParamsDefault(); + params.buffer_exhausted_policy = (PerfettoDsBufferExhaustedPolicy)buffer_exhausted_policy; + + params.user_arg = reinterpret_cast<void*>(datasource.get()); + + params.on_setup_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex inst_id, + void* ds_config, size_t ds_config_size, void* user_arg, + struct PerfettoDsOnSetupArgs*) -> void* { + JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); + + auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg); + + ScopedLocalRef<jobject> java_data_source_instance(env, + datasource->newInstance(env, ds_config, + ds_config_size, + inst_id)); + + auto* datasource_instance = + new PerfettoDataSourceInstance(env, java_data_source_instance.get(), inst_id); + + return static_cast<void*>(datasource_instance); + }; + + params.on_create_tls_cb = [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id, + struct PerfettoDsTracerImpl* tracer, void* user_arg) -> void* { + JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); + + auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg); + + jobject java_tls_state = datasource->createTlsStateGlobalRef(env, inst_id); + + auto* tls_state = new TlsState(java_tls_state); + + return static_cast<void*>(tls_state); + }; + + params.on_delete_tls_cb = [](void* ptr) { + JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); + + TlsState* tls_state = reinterpret_cast<TlsState*>(ptr); + env->DeleteGlobalRef(tls_state->jobj); + delete tls_state; + }; + + params.on_create_incr_cb = [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id, + struct PerfettoDsTracerImpl* tracer, void* user_arg) -> void* { + JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); + + auto* datasource = reinterpret_cast<PerfettoDataSource*>(user_arg); + jobject java_incr_state = datasource->createIncrementalStateGlobalRef(env, inst_id); + + auto* incr_state = new IncrementalState(java_incr_state); + return static_cast<void*>(incr_state); + }; + + params.on_delete_incr_cb = [](void* ptr) { + JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); + + IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(ptr); + env->DeleteGlobalRef(incr_state->jobj); + delete incr_state; + }; + + params.on_start_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, void* inst_ctx, + struct PerfettoDsOnStartArgs*) { + JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); + + auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx); + datasource_instance->onStart(env); + }; + + params.on_flush_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, void* inst_ctx, + struct PerfettoDsOnFlushArgs*) { + JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); + + auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx); + datasource_instance->onFlush(env); + }; + + params.on_stop_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex inst_id, void* user_arg, + void* inst_ctx, struct PerfettoDsOnStopArgs*) { + JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); + + auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx); + datasource_instance->onStop(env); + }; + + params.on_destroy_cb = [](struct PerfettoDsImpl* ds_impl, void* user_arg, + void* inst_ctx) -> void { + auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx); + delete datasource_instance; + }; + + PerfettoDsRegister(&datasource->dataSource, datasource->dataSourceName.c_str(), params); +} + +jobject nativeGetPerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr, + PerfettoDsInstanceIndex instance_idx) { + sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr); + auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>( + PerfettoDsImplGetInstanceLocked(datasource->dataSource.impl, instance_idx)); + + if (datasource_instance == nullptr) { + // datasource instance doesn't exist + return nullptr; + } + + return datasource_instance->GetJavaDataSourceInstance(); +} + +void nativeReleasePerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr, + PerfettoDsInstanceIndex instance_idx) { + sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr); + PerfettoDsImplReleaseInstanceLocked(datasource->dataSource.impl, instance_idx); +} + +const JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + {"nativeCreate", "(Landroid/tracing/perfetto/DataSource;Ljava/lang/String;)J", + (void*)nativeCreate}, + {"nativeTrace", "(JLandroid/tracing/perfetto/TraceFunction;)V", (void*)nativeTrace}, + {"nativeFlushAll", "(J)V", (void*)nativeFlushAll}, + {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer}, + {"nativeRegisterDataSource", "(JI)V", (void*)nativeRegisterDataSource}, + {"nativeGetPerfettoInstanceLocked", "(JI)Landroid/tracing/perfetto/DataSourceInstance;", + (void*)nativeGetPerfettoInstanceLocked}, + {"nativeReleasePerfettoInstanceLocked", "(JI)V", + (void*)nativeReleasePerfettoInstanceLocked}, +}; + +const JNINativeMethod gMethodsTracingContext[] = { + /* name, signature, funcPtr */ + {"nativeFlush", "(Landroid/tracing/perfetto/TracingContext;J)V", (void*)nativeFlush}, +}; + +int register_android_tracing_PerfettoDataSource(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "android/tracing/perfetto/DataSource", gMethods, + NELEM(gMethods)); + + LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); + + res = jniRegisterNativeMethods(env, "android/tracing/perfetto/TracingContext", + gMethodsTracingContext, NELEM(gMethodsTracingContext)); + + LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); + + if (env->GetJavaVM(&gVm) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JavaVM from JNIEnv: %p", env); + } + + jclass clazz = env->FindClass("android/tracing/perfetto/DataSource"); + gPerfettoDataSourceClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + gPerfettoDataSourceClassInfo.createInstance = + env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createInstance", + "([BI)Landroid/tracing/perfetto/DataSourceInstance;"); + gPerfettoDataSourceClassInfo.createTlsState = + env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createTlsState", + "(Landroid/tracing/perfetto/CreateTlsStateArgs;)Ljava/lang/Object;"); + gPerfettoDataSourceClassInfo.createIncrementalState = + env->GetMethodID(gPerfettoDataSourceClassInfo.clazz, "createIncrementalState", + "(Landroid/tracing/perfetto/CreateIncrementalStateArgs;)Ljava/lang/" + "Object;"); + + clazz = env->FindClass("android/tracing/perfetto/TracingContext"); + gTracingContextClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + gTracingContextClassInfo.init = env->GetMethodID(gTracingContextClassInfo.clazz, "<init>", + "(JLjava/lang/Object;Ljava/lang/Object;)V"); + gTracingContextClassInfo.getAndClearAllPendingTracePackets = + env->GetMethodID(gTracingContextClassInfo.clazz, "getAndClearAllPendingTracePackets", + "()[[B"); + + clazz = env->FindClass("android/tracing/perfetto/CreateTlsStateArgs"); + gCreateTlsStateArgsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + gCreateTlsStateArgsClassInfo.init = + env->GetMethodID(gCreateTlsStateArgsClassInfo.clazz, "<init>", + "(Landroid/tracing/perfetto/DataSource;I)V"); + + clazz = env->FindClass("android/tracing/perfetto/CreateIncrementalStateArgs"); + gCreateIncrementalStateArgsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + gCreateIncrementalStateArgsClassInfo.init = + env->GetMethodID(gCreateIncrementalStateArgsClassInfo.clazz, "<init>", + "(Landroid/tracing/perfetto/DataSource;I)V"); + + return 0; +} + +} // namespace android
\ No newline at end of file diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h new file mode 100644 index 000000000000..4ddf1d8d4512 --- /dev/null +++ b/core/jni/android_tracing_PerfettoDataSource.h @@ -0,0 +1,59 @@ +/* + * Copyright 2023 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 "Perfetto" + +#include <android_runtime/AndroidRuntime.h> +#include <android_runtime/Log.h> +#include <nativehelper/JNIHelp.h> +#include <perfetto/public/data_source.h> +#include <perfetto/public/producer.h> +#include <perfetto/public/protos/trace/test_event.pzc.h> +#include <perfetto/public/protos/trace/trace_packet.pzc.h> +#include <perfetto/tracing.h> +#include <utils/Log.h> +#include <utils/RefBase.h> + +#include <sstream> +#include <thread> + +#include "android_tracing_PerfettoDataSourceInstance.h" +#include "core_jni_helpers.h" + +namespace android { + +class PerfettoDataSource : public virtual RefBase { +public: + const std::string dataSourceName; + struct PerfettoDs dataSource = PERFETTO_DS_INIT(); + + PerfettoDataSource(JNIEnv* env, jobject java_data_source, std::string data_source_name); + ~PerfettoDataSource(); + + jobject newInstance(JNIEnv* env, void* ds_config, size_t ds_config_size, + PerfettoDsInstanceIndex inst_id); + + jobject createTlsStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id); + jobject createIncrementalStateGlobalRef(JNIEnv* env, PerfettoDsInstanceIndex inst_id); + void trace(JNIEnv* env, jobject trace_function); + void flushAll(); + +private: + jobject mJavaDataSource; + std::map<PerfettoDsInstanceIndex, PerfettoDataSourceInstance*> mInstances; +}; + +} // namespace android
\ No newline at end of file diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.cpp b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp new file mode 100644 index 000000000000..e659bf1c55e9 --- /dev/null +++ b/core/jni/android_tracing_PerfettoDataSourceInstance.cpp @@ -0,0 +1,138 @@ +/* + * Copyright 2023 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 "Perfetto" + +#include "android_tracing_PerfettoDataSourceInstance.h" + +#include <android_runtime/AndroidRuntime.h> +#include <android_runtime/Log.h> +#include <nativehelper/JNIHelp.h> +#include <perfetto/public/data_source.h> +#include <perfetto/public/producer.h> +#include <perfetto/public/protos/trace/test_event.pzc.h> +#include <perfetto/public/protos/trace/trace_packet.pzc.h> +#include <perfetto/tracing.h> +#include <utils/Log.h> +#include <utils/RefBase.h> + +#include <sstream> +#include <thread> + +#include "core_jni_helpers.h" + +namespace android { + +static struct { + jclass clazz; + jmethodID init; +} gStartCallbackArgumentsClassInfo; + +static struct { + jclass clazz; + jmethodID init; +} gFlushCallbackArgumentsClassInfo; + +static struct { + jclass clazz; + jmethodID init; +} gStopCallbackArgumentsClassInfo; + +static JavaVM* gVm; + +void callJavaMethodWithArgsObject(JNIEnv* env, jobject classRef, jmethodID method, jobject args) { + ScopedLocalRef<jobject> localClassRef(env, env->NewLocalRef(classRef)); + + if (localClassRef == nullptr) { + ALOGE("Weak reference went out of scope"); + return; + } + + env->CallVoidMethod(localClassRef.get(), method, args); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + LOGE_EX(env); + env->ExceptionClear(); + } +} + +PerfettoDataSourceInstance::PerfettoDataSourceInstance(JNIEnv* env, jobject javaDataSourceInstance, + PerfettoDsInstanceIndex inst_idx) + : inst_idx(inst_idx), mJavaDataSourceInstance(env->NewGlobalRef(javaDataSourceInstance)) {} + +PerfettoDataSourceInstance::~PerfettoDataSourceInstance() { + JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); + env->DeleteGlobalRef(mJavaDataSourceInstance); +} + +void PerfettoDataSourceInstance::onStart(JNIEnv* env) { + ScopedLocalRef<jobject> args(env, + env->NewObject(gStartCallbackArgumentsClassInfo.clazz, + gStartCallbackArgumentsClassInfo.init)); + jclass cls = env->GetObjectClass(mJavaDataSourceInstance); + jmethodID mid = env->GetMethodID(cls, "onStart", + "(Landroid/tracing/perfetto/StartCallbackArguments;)V"); + + callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get()); +} + +void PerfettoDataSourceInstance::onFlush(JNIEnv* env) { + ScopedLocalRef<jobject> args(env, + env->NewObject(gFlushCallbackArgumentsClassInfo.clazz, + gFlushCallbackArgumentsClassInfo.init)); + jclass cls = env->GetObjectClass(mJavaDataSourceInstance); + jmethodID mid = env->GetMethodID(cls, "onFlush", + "(Landroid/tracing/perfetto/FlushCallbackArguments;)V"); + + callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get()); +} + +void PerfettoDataSourceInstance::onStop(JNIEnv* env) { + ScopedLocalRef<jobject> args(env, + env->NewObject(gStopCallbackArgumentsClassInfo.clazz, + gStopCallbackArgumentsClassInfo.init)); + jclass cls = env->GetObjectClass(mJavaDataSourceInstance); + jmethodID mid = + env->GetMethodID(cls, "onStop", "(Landroid/tracing/perfetto/StopCallbackArguments;)V"); + + callJavaMethodWithArgsObject(env, mJavaDataSourceInstance, mid, args.get()); +} + +int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env) { + if (env->GetJavaVM(&gVm) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JavaVM from JNIEnv: %p", env); + } + + jclass clazz = env->FindClass("android/tracing/perfetto/StartCallbackArguments"); + gStartCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + gStartCallbackArgumentsClassInfo.init = + env->GetMethodID(gStartCallbackArgumentsClassInfo.clazz, "<init>", "()V"); + + clazz = env->FindClass("android/tracing/perfetto/FlushCallbackArguments"); + gFlushCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + gFlushCallbackArgumentsClassInfo.init = + env->GetMethodID(gFlushCallbackArgumentsClassInfo.clazz, "<init>", "()V"); + + clazz = env->FindClass("android/tracing/perfetto/StopCallbackArguments"); + gStopCallbackArgumentsClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + gStopCallbackArgumentsClassInfo.init = + env->GetMethodID(gStopCallbackArgumentsClassInfo.clazz, "<init>", "()V"); + + return 0; +} + +} // namespace android
\ No newline at end of file diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.h b/core/jni/android_tracing_PerfettoDataSourceInstance.h new file mode 100644 index 000000000000..d57765565d8a --- /dev/null +++ b/core/jni/android_tracing_PerfettoDataSourceInstance.h @@ -0,0 +1,59 @@ +/* + * Copyright 2023 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 "Perfetto" + +#include <android_runtime/AndroidRuntime.h> +#include <android_runtime/Log.h> +#include <nativehelper/JNIHelp.h> +#include <perfetto/public/data_source.h> +#include <perfetto/public/producer.h> +#include <perfetto/public/protos/trace/test_event.pzc.h> +#include <perfetto/public/protos/trace/trace_packet.pzc.h> +#include <perfetto/tracing.h> +#include <utils/Log.h> +#include <utils/RefBase.h> + +#include <sstream> +#include <thread> + +#include "core_jni_helpers.h" + +namespace android { + +class PerfettoDataSourceInstance { +public: + PerfettoDataSourceInstance(JNIEnv* env, jobject javaDataSourceInstance, + PerfettoDsInstanceIndex inst_idx); + ~PerfettoDataSourceInstance(); + + void onStart(JNIEnv* env); + void onFlush(JNIEnv* env); + void onStop(JNIEnv* env); + + jobject GetJavaDataSourceInstance() { + return mJavaDataSourceInstance; + } + + PerfettoDsInstanceIndex getIndex() { + return inst_idx; + } + +private: + PerfettoDsInstanceIndex inst_idx; + jobject mJavaDataSourceInstance; +}; +} // namespace android
\ No newline at end of file diff --git a/core/jni/android_tracing_PerfettoProducer.cpp b/core/jni/android_tracing_PerfettoProducer.cpp new file mode 100644 index 000000000000..ce72f5893c19 --- /dev/null +++ b/core/jni/android_tracing_PerfettoProducer.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2023 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 "Perfetto" + +#include <android_runtime/AndroidRuntime.h> +#include <android_runtime/Log.h> +#include <nativehelper/JNIHelp.h> +#include <perfetto/public/data_source.h> +#include <perfetto/public/producer.h> +#include <perfetto/public/protos/trace/test_event.pzc.h> +#include <perfetto/public/protos/trace/trace_packet.pzc.h> +#include <perfetto/tracing.h> +#include <utils/Log.h> +#include <utils/RefBase.h> + +#include <sstream> +#include <thread> + +#include "android_tracing_PerfettoDataSource.h" +#include "core_jni_helpers.h" + +namespace android { + +void perfettoProducerInit(JNIEnv* env, jclass clazz, int backends) { + struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT(); + args.backends = (PerfettoBackendTypes)backends; + PerfettoProducerInit(args); +} + +const JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + {"nativePerfettoProducerInit", "(I)V", (void*)perfettoProducerInit}, +}; + +int register_android_tracing_PerfettoProducer(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "android/tracing/perfetto/Producer", gMethods, + NELEM(gMethods)); + + LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); + + return 0; +} + +} // namespace android
\ No newline at end of file diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index c0581746e6f6..531756ef2302 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -83,6 +83,10 @@ android_test { "com.android.text.flags-aconfig-java", "flag-junit", "ravenwood-junit", + "perfetto_trace_java_protos", + "flickerlib-parsers", + "flickerlib-trace_processor_shell", + "mockito-target-extended-minus-junit4", ], libs: [ diff --git a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java new file mode 100644 index 000000000000..bd2f36fb5198 --- /dev/null +++ b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java @@ -0,0 +1,664 @@ +/* + * Copyright (C) 2023 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.tracing.perfetto; + +import static android.internal.perfetto.protos.PerfettoTrace.TestEvent.PAYLOAD; +import static android.internal.perfetto.protos.PerfettoTrace.TestEvent.TestPayload.SINGLE_INT; +import static android.internal.perfetto.protos.PerfettoTrace.TracePacket.FOR_TESTING; + +import static java.io.File.createTempFile; +import static java.nio.file.Files.createTempDirectory; + +import android.internal.perfetto.protos.PerfettoTrace; +import android.tools.common.ScenarioBuilder; +import android.tools.common.Tag; +import android.tools.common.io.TraceType; +import android.tools.device.traces.TraceConfig; +import android.tools.device.traces.TraceConfigs; +import android.tools.device.traces.io.ResultReader; +import android.tools.device.traces.io.ResultWriter; +import android.tools.device.traces.monitors.PerfettoTraceMonitor; +import android.tools.device.traces.monitors.TraceMonitor; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import androidx.test.runner.AndroidJUnit4; + +import com.google.common.truth.Truth; +import com.google.protobuf.InvalidProtocolBufferException; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import perfetto.protos.PerfettoConfig; +import perfetto.protos.TracePacketOuterClass; + +@RunWith(AndroidJUnit4.class) +public class DataSourceTest { + private final File mTracingDirectory = createTempDirectory("temp").toFile(); + + private final ResultWriter mWriter = new ResultWriter() + .forScenario(new ScenarioBuilder() + .forClass(createTempFile("temp", "").getName()).build()) + .withOutputDir(mTracingDirectory) + .setRunComplete(); + + private final TraceConfigs mTraceConfig = new TraceConfigs( + new TraceConfig(false, true, false), + new TraceConfig(false, true, false), + new TraceConfig(false, true, false), + new TraceConfig(false, true, false) + ); + + private static TestDataSource sTestDataSource; + + private static TestDataSource.DataSourceInstanceProvider sInstanceProvider; + private static TestDataSource.TlsStateProvider sTlsStateProvider; + private static TestDataSource.IncrementalStateProvider sIncrementalStateProvider; + + public DataSourceTest() throws IOException {} + + @BeforeClass + public static void beforeAll() { + Producer.init(InitArguments.DEFAULTS); + setupProviders(); + sTestDataSource = new TestDataSource( + (ds, idx, configStream) -> sInstanceProvider.provide(ds, idx, configStream), + args -> sTlsStateProvider.provide(args), + args -> sIncrementalStateProvider.provide(args)); + sTestDataSource.register(DataSourceParams.DEFAULTS); + } + + private static void setupProviders() { + sInstanceProvider = (ds, idx, configStream) -> + new TestDataSource.TestDataSourceInstance(ds, idx); + sTlsStateProvider = args -> new TestDataSource.TestTlsState(); + sIncrementalStateProvider = args -> new TestDataSource.TestIncrementalState(); + } + + @Before + public void setup() { + setupProviders(); + } + + @Test + public void canTraceData() throws InvalidProtocolBufferException { + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + + sTestDataSource.trace((ctx) -> { + final ProtoOutputStream protoOutputStream = ctx.newTracePacket(); + long forTestingToken = protoOutputStream.start(FOR_TESTING); + long payloadToken = protoOutputStream.start(PAYLOAD); + protoOutputStream.write(SINGLE_INT, 10); + protoOutputStream.end(payloadToken); + protoOutputStream.end(forTestingToken); + }); + } finally { + traceMonitor.stop(mWriter); + } + + final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig); + final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL); + assert rawProtoFromFile != null; + final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace + .parseFrom(rawProtoFromFile); + + Truth.assertThat(trace.getPacketCount()).isGreaterThan(0); + final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList() + .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList(); + final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream() + .filter(it -> it.getForTesting().getPayload().getSingleInt() == 10).toList(); + Truth.assertThat(matchingPackets).hasSize(1); + } + + @Test + public void canUseTlsStateForCustomState() { + final int expectedStateTestValue = 10; + final AtomicInteger actualStateTestValue = new AtomicInteger(); + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + + sTestDataSource.trace((ctx) -> { + TestDataSource.TestTlsState state = ctx.getCustomTlsState(); + state.testStateValue = expectedStateTestValue; + }); + + sTestDataSource.trace((ctx) -> { + TestDataSource.TestTlsState state = ctx.getCustomTlsState(); + actualStateTestValue.set(state.testStateValue); + }); + } finally { + traceMonitor.stop(mWriter); + } + + Truth.assertThat(actualStateTestValue.get()).isEqualTo(expectedStateTestValue); + } + + @Test + public void eachInstanceHasOwnTlsState() { + final int[] expectedStateTestValues = new int[] { 1, 2 }; + final int[] actualStateTestValues = new int[] { 0, 0 }; + + final TraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + final TraceMonitor traceMonitor2 = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor1.start(); + try { + traceMonitor2.start(); + + AtomicInteger index = new AtomicInteger(0); + sTestDataSource.trace((ctx) -> { + TestDataSource.TestTlsState state = ctx.getCustomTlsState(); + state.testStateValue = expectedStateTestValues[index.getAndIncrement()]; + }); + + index.set(0); + sTestDataSource.trace((ctx) -> { + TestDataSource.TestTlsState state = ctx.getCustomTlsState(); + actualStateTestValues[index.getAndIncrement()] = state.testStateValue; + }); + } finally { + traceMonitor1.stop(mWriter); + } + } finally { + traceMonitor2.stop(mWriter); + } + + Truth.assertThat(actualStateTestValues[0]).isEqualTo(expectedStateTestValues[0]); + Truth.assertThat(actualStateTestValues[1]).isEqualTo(expectedStateTestValues[1]); + } + + @Test + public void eachThreadHasOwnTlsState() throws InterruptedException { + final int thread1ExpectedStateValue = 1; + final int thread2ExpectedStateValue = 2; + + final AtomicInteger thread1ActualStateValue = new AtomicInteger(); + final AtomicInteger thread2ActualStateValue = new AtomicInteger(); + + final CountDownLatch setUpLatch = new CountDownLatch(2); + final CountDownLatch setStateLatch = new CountDownLatch(2); + final CountDownLatch setOutStateLatch = new CountDownLatch(2); + + final RunnableCreator createTask = (stateValue, stateOut) -> () -> { + Producer.init(InitArguments.DEFAULTS); + + setUpLatch.countDown(); + + try { + setUpLatch.await(3, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + sTestDataSource.trace((ctx) -> { + TestDataSource.TestTlsState state = ctx.getCustomTlsState(); + state.testStateValue = stateValue; + setStateLatch.countDown(); + }); + + try { + setStateLatch.await(3, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + sTestDataSource.trace((ctx) -> { + stateOut.set(ctx.getCustomTlsState().testStateValue); + setOutStateLatch.countDown(); + }); + }; + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + + new Thread( + createTask.create(thread1ExpectedStateValue, thread1ActualStateValue)).start(); + new Thread( + createTask.create(thread2ExpectedStateValue, thread2ActualStateValue)).start(); + + setOutStateLatch.await(3, TimeUnit.SECONDS); + + } finally { + traceMonitor.stop(mWriter); + } + + Truth.assertThat(thread1ActualStateValue.get()).isEqualTo(thread1ExpectedStateValue); + Truth.assertThat(thread2ActualStateValue.get()).isEqualTo(thread2ExpectedStateValue); + } + + @Test + public void incrementalStateIsReset() throws InterruptedException { + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()) + .setIncrementalTimeout(10) + .build(); + + final AtomicInteger testStateValue = new AtomicInteger(); + try { + traceMonitor.start(); + + sTestDataSource.trace(ctx -> ctx.getIncrementalState().testStateValue = 1); + + // Timeout to make sure the incremental state is cleared. + Thread.sleep(1000); + + sTestDataSource.trace(ctx -> + testStateValue.set(ctx.getIncrementalState().testStateValue)); + } finally { + traceMonitor.stop(mWriter); + } + + Truth.assertThat(testStateValue.get()).isNotEqualTo(1); + } + + @Test + public void getInstanceConfigOnCreateInstance() throws IOException { + final int expectedDummyIntValue = 10; + AtomicReference<ProtoInputStream> configStream = new AtomicReference<>(); + sInstanceProvider = (ds, idx, config) -> { + configStream.set(config); + return new TestDataSource.TestDataSourceInstance(ds, idx); + }; + + final TraceMonitor monitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name) + .setForTesting(PerfettoConfig.TestConfig.newBuilder().setDummyFields( + PerfettoConfig.TestConfig.DummyFields.newBuilder() + .setFieldInt32(expectedDummyIntValue) + .build()) + .build()) + .build()) + .build(); + + try { + monitor.start(); + } finally { + monitor.stop(mWriter); + } + + int configDummyIntValue = 0; + while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (configStream.get().getFieldNumber() + == (int) PerfettoTrace.DataSourceConfig.FOR_TESTING) { + final long forTestingToken = configStream.get() + .start(PerfettoTrace.DataSourceConfig.FOR_TESTING); + while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (configStream.get().getFieldNumber() + == (int) PerfettoTrace.TestConfig.DUMMY_FIELDS) { + final long dummyFieldsToken = configStream.get() + .start(PerfettoTrace.TestConfig.DUMMY_FIELDS); + while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (configStream.get().getFieldNumber() + == (int) PerfettoTrace.TestConfig.DummyFields.FIELD_INT32) { + int val = configStream.get().readInt( + PerfettoTrace.TestConfig.DummyFields.FIELD_INT32); + if (val != 0) { + configDummyIntValue = val; + break; + } + } + } + configStream.get().end(dummyFieldsToken); + break; + } + } + configStream.get().end(forTestingToken); + break; + } + } + + Truth.assertThat(configDummyIntValue).isEqualTo(expectedDummyIntValue); + } + + @Test + public void multipleTraceInstances() throws IOException, InterruptedException { + final int instanceCount = 3; + + final List<TraceMonitor> monitors = new ArrayList<>(); + final List<ResultWriter> writers = new ArrayList<>(); + + for (int i = 0; i < instanceCount; i++) { + final ResultWriter writer = new ResultWriter() + .forScenario(new ScenarioBuilder() + .forClass(createTempFile("temp", "").getName()).build()) + .withOutputDir(mTracingDirectory) + .setRunComplete(); + writers.add(writer); + } + + // Start at 1 because 0 is considered null value so payload will be ignored in that case + TestDataSource.TestTlsState.lastIndex = 1; + + final AtomicInteger traceCallCount = new AtomicInteger(); + final CountDownLatch latch = new CountDownLatch(instanceCount); + + try { + // Start instances + for (int i = 0; i < instanceCount; i++) { + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + monitors.add(traceMonitor); + traceMonitor.start(); + } + + // Trace the stateIndex of the tracing instance. + sTestDataSource.trace(ctx -> { + final int testIntValue = ctx.getCustomTlsState().stateIndex; + traceCallCount.incrementAndGet(); + + final ProtoOutputStream os = ctx.newTracePacket(); + long forTestingToken = os.start(FOR_TESTING); + long payloadToken = os.start(PAYLOAD); + os.write(SINGLE_INT, testIntValue); + os.end(payloadToken); + os.end(forTestingToken); + + latch.countDown(); + }); + } finally { + // Stop instances + for (int i = 0; i < instanceCount; i++) { + final TraceMonitor monitor = monitors.get(i); + final ResultWriter writer = writers.get(i); + monitor.stop(writer); + } + } + + latch.await(3, TimeUnit.SECONDS); + Truth.assertThat(traceCallCount.get()).isEqualTo(instanceCount); + + for (int i = 0; i < instanceCount; i++) { + final int expectedTracedValue = i + 1; + final ResultWriter writer = writers.get(i); + final ResultReader reader = new ResultReader(writer.write(), mTraceConfig); + final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL); + assert rawProtoFromFile != null; + final perfetto.protos.TraceOuterClass.Trace trace = + perfetto.protos.TraceOuterClass.Trace.parseFrom(rawProtoFromFile); + + Truth.assertThat(trace.getPacketCount()).isGreaterThan(0); + final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList() + .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList(); + Truth.assertWithMessage("One packet has for testing data") + .that(tracePackets).hasSize(1); + + final List<TracePacketOuterClass.TracePacket> matchingPackets = + tracePackets.stream() + .filter(it -> it.getForTesting().getPayload() + .getSingleInt() == expectedTracedValue).toList(); + Truth.assertWithMessage( + "One packet has testing data with a payload with the expected value") + .that(matchingPackets).hasSize(1); + } + } + + @Test + public void onStartCallbackTriggered() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + + final AtomicBoolean callbackCalled = new AtomicBoolean(false); + sInstanceProvider = (ds, idx, config) -> new TestDataSource.TestDataSourceInstance( + ds, + idx, + (args) -> { + callbackCalled.set(true); + latch.countDown(); + }, + (args) -> {}, + (args) -> {} + ); + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + Truth.assertThat(callbackCalled.get()).isFalse(); + try { + traceMonitor.start(); + latch.await(3, TimeUnit.SECONDS); + Truth.assertThat(callbackCalled.get()).isTrue(); + } finally { + traceMonitor.stop(mWriter); + } + } + + @Test + public void onFlushCallbackTriggered() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicBoolean callbackCalled = new AtomicBoolean(false); + sInstanceProvider = (ds, idx, config) -> + new TestDataSource.TestDataSourceInstance( + ds, + idx, + (args) -> {}, + (args) -> { + callbackCalled.set(true); + latch.countDown(); + }, + (args) -> {} + ); + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + Truth.assertThat(callbackCalled.get()).isFalse(); + sTestDataSource.trace((ctx) -> { + final ProtoOutputStream protoOutputStream = ctx.newTracePacket(); + long forTestingToken = protoOutputStream.start(FOR_TESTING); + long payloadToken = protoOutputStream.start(PAYLOAD); + protoOutputStream.write(SINGLE_INT, 10); + protoOutputStream.end(payloadToken); + protoOutputStream.end(forTestingToken); + }); + } finally { + traceMonitor.stop(mWriter); + } + + latch.await(3, TimeUnit.SECONDS); + Truth.assertThat(callbackCalled.get()).isTrue(); + } + + @Test + public void onStopCallbackTriggered() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicBoolean callbackCalled = new AtomicBoolean(false); + sInstanceProvider = (ds, idx, config) -> + new TestDataSource.TestDataSourceInstance( + ds, + idx, + (args) -> {}, + (args) -> {}, + (args) -> { + callbackCalled.set(true); + latch.countDown(); + } + ); + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + Truth.assertThat(callbackCalled.get()).isFalse(); + } finally { + traceMonitor.stop(mWriter); + } + + latch.await(3, TimeUnit.SECONDS); + Truth.assertThat(callbackCalled.get()).isTrue(); + } + + @Test + public void canUseDataSourceInstanceToCreateTlsState() throws InvalidProtocolBufferException { + final Object testObject = new Object(); + + sInstanceProvider = (ds, idx, configStream) -> { + final TestDataSource.TestDataSourceInstance dsInstance = + new TestDataSource.TestDataSourceInstance(ds, idx); + dsInstance.testObject = testObject; + return dsInstance; + }; + + sTlsStateProvider = args -> { + final TestDataSource.TestTlsState tlsState = new TestDataSource.TestTlsState(); + + try (TestDataSource.TestDataSourceInstance dataSourceInstance = + args.getDataSourceInstanceLocked()) { + if (dataSourceInstance != null) { + tlsState.testStateValue = dataSourceInstance.testObject.hashCode(); + } + } + + return tlsState; + }; + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + sTestDataSource.trace((ctx) -> { + final ProtoOutputStream protoOutputStream = ctx.newTracePacket(); + long forTestingToken = protoOutputStream.start(FOR_TESTING); + long payloadToken = protoOutputStream.start(PAYLOAD); + protoOutputStream.write(SINGLE_INT, ctx.getCustomTlsState().testStateValue); + protoOutputStream.end(payloadToken); + protoOutputStream.end(forTestingToken); + }); + } finally { + traceMonitor.stop(mWriter); + } + + final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig); + final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL); + assert rawProtoFromFile != null; + final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace + .parseFrom(rawProtoFromFile); + + Truth.assertThat(trace.getPacketCount()).isGreaterThan(0); + final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList() + .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList(); + final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream() + .filter(it -> it.getForTesting().getPayload().getSingleInt() + == testObject.hashCode()).toList(); + Truth.assertThat(matchingPackets).hasSize(1); + } + + @Test + public void canUseDataSourceInstanceToCreateIncrementalState() + throws InvalidProtocolBufferException { + final Object testObject = new Object(); + + sInstanceProvider = (ds, idx, configStream) -> { + final TestDataSource.TestDataSourceInstance dsInstance = + new TestDataSource.TestDataSourceInstance(ds, idx); + dsInstance.testObject = testObject; + return dsInstance; + }; + + sIncrementalStateProvider = args -> { + final TestDataSource.TestIncrementalState incrementalState = + new TestDataSource.TestIncrementalState(); + + try (TestDataSource.TestDataSourceInstance dataSourceInstance = + args.getDataSourceInstanceLocked()) { + if (dataSourceInstance != null) { + incrementalState.testStateValue = dataSourceInstance.testObject.hashCode(); + } + } + + return incrementalState; + }; + + final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() + .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder() + .setName(sTestDataSource.name).build()).build(); + + try { + traceMonitor.start(); + sTestDataSource.trace((ctx) -> { + final ProtoOutputStream protoOutputStream = ctx.newTracePacket(); + long forTestingToken = protoOutputStream.start(FOR_TESTING); + long payloadToken = protoOutputStream.start(PAYLOAD); + protoOutputStream.write(SINGLE_INT, ctx.getIncrementalState().testStateValue); + protoOutputStream.end(payloadToken); + protoOutputStream.end(forTestingToken); + }); + } finally { + traceMonitor.stop(mWriter); + } + + final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig); + final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL); + assert rawProtoFromFile != null; + final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace + .parseFrom(rawProtoFromFile); + + Truth.assertThat(trace.getPacketCount()).isGreaterThan(0); + final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList() + .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList(); + final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream() + .filter(it -> it.getForTesting().getPayload().getSingleInt() + == testObject.hashCode()).toList(); + Truth.assertThat(matchingPackets).hasSize(1); + } + + interface RunnableCreator { + Runnable create(int state, AtomicInteger stateOut); + } +} diff --git a/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java b/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java new file mode 100644 index 000000000000..d78f78b1cb0e --- /dev/null +++ b/core/tests/coretests/src/android/tracing/perfetto/TestDataSource.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2023 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.tracing.perfetto; + +import android.util.proto.ProtoInputStream; + +import java.util.UUID; +import java.util.function.Consumer; + +public class TestDataSource extends DataSource<TestDataSource.TestDataSourceInstance, + TestDataSource.TestTlsState, TestDataSource.TestIncrementalState> { + private final DataSourceInstanceProvider mDataSourceInstanceProvider; + private final TlsStateProvider mTlsStateProvider; + private final IncrementalStateProvider mIncrementalStateProvider; + + interface DataSourceInstanceProvider { + TestDataSourceInstance provide( + TestDataSource dataSource, int instanceIndex, ProtoInputStream configStream); + } + + interface TlsStateProvider { + TestTlsState provide(CreateTlsStateArgs<TestDataSourceInstance> args); + } + + interface IncrementalStateProvider { + TestIncrementalState provide(CreateIncrementalStateArgs<TestDataSourceInstance> args); + } + + public TestDataSource() { + this((ds, idx, config) -> new TestDataSourceInstance(ds, idx), + args -> new TestTlsState(), args -> new TestIncrementalState()); + } + + public TestDataSource( + DataSourceInstanceProvider dataSourceInstanceProvider, + TlsStateProvider tlsStateProvider, + IncrementalStateProvider incrementalStateProvider + ) { + super("android.tracing.perfetto.TestDataSource#" + UUID.randomUUID().toString()); + this.mDataSourceInstanceProvider = dataSourceInstanceProvider; + this.mTlsStateProvider = tlsStateProvider; + this.mIncrementalStateProvider = incrementalStateProvider; + } + + @Override + public TestDataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) { + return mDataSourceInstanceProvider.provide(this, instanceIndex, configStream); + } + + @Override + public TestTlsState createTlsState(CreateTlsStateArgs args) { + return mTlsStateProvider.provide(args); + } + + @Override + public TestIncrementalState createIncrementalState(CreateIncrementalStateArgs args) { + return mIncrementalStateProvider.provide(args); + } + + public static class TestTlsState { + public int testStateValue; + public int stateIndex = lastIndex++; + + public static int lastIndex = 0; + } + + public static class TestIncrementalState { + public int testStateValue; + } + + public static class TestDataSourceInstance extends DataSourceInstance { + public Object testObject; + Consumer<StartCallbackArguments> mStartCallback; + Consumer<FlushCallbackArguments> mFlushCallback; + Consumer<StopCallbackArguments> mStopCallback; + + public TestDataSourceInstance(DataSource dataSource, int instanceIndex) { + this(dataSource, instanceIndex, args -> {}, args -> {}, args -> {}); + } + + public TestDataSourceInstance( + DataSource dataSource, + int instanceIndex, + Consumer<StartCallbackArguments> startCallback, + Consumer<FlushCallbackArguments> flushCallback, + Consumer<StopCallbackArguments> stopCallback) { + super(dataSource, instanceIndex); + this.mStartCallback = startCallback; + this.mFlushCallback = flushCallback; + this.mStopCallback = stopCallback; + } + + @Override + public void onStart(StartCallbackArguments args) { + this.mStartCallback.accept(args); + } + + @Override + public void onFlush(FlushCallbackArguments args) { + this.mFlushCallback.accept(args); + } + + @Override + public void onStop(StopCallbackArguments args) { + this.mStopCallback.accept(args); + } + } +} diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt index 03b268d87d01..6339a8703f01 100644 --- a/framework-jarjar-rules.txt +++ b/framework-jarjar-rules.txt @@ -8,3 +8,6 @@ rule com.android.server.vcn.util.** com.android.server.vcn.repackaged.util.@1 # for modules-utils-build dependency rule com.android.modules.utils.build.** android.internal.modules.utils.build.@1 + +# For Perfetto proto dependencies +rule perfetto.protos.** android.internal.perfetto.protos.@1 diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 066f38b61eec..83d555cbdecd 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -822,11 +822,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Checks if container should be updated before apply new parentInfo. final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo); taskContainer.updateTaskFragmentParentInfo(parentInfo); - if (!taskContainer.isVisible()) { - // Don't update containers if the task is not visible. We only update containers when - // parentInfo#isVisibleRequested is true. - return; - } // If the last direct activity of the host task is dismissed and the overlay container is // the only taskFragment, the overlay container should also be dismissed. diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index bc921010b469..4e7b76057b5d 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -475,8 +475,10 @@ public class OverlayPresentationTest { @Test public void testOnTaskFragmentParentInfoChanged_positionOnlyChange_earlyReturn() { final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test"); - final TaskContainer taskContainer = overlayContainer.getTaskContainer(); + + assertThat(taskContainer.getOverlayContainer()).isEqualTo(overlayContainer); + spyOn(taskContainer); final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties(); final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo( @@ -495,6 +497,30 @@ public class OverlayPresentationTest { .that(taskContainer.getOverlayContainer()).isNull(); } + @Test + public void testOnTaskFragmentParentInfoChanged_invisibleTask_callDismissOverlayContainer() { + final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test"); + final TaskContainer taskContainer = overlayContainer.getTaskContainer(); + + assertThat(taskContainer.getOverlayContainer()).isEqualTo(overlayContainer); + + spyOn(taskContainer); + final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties(); + final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo( + new Configuration(taskProperties.getConfiguration()), taskProperties.getDisplayId(), + false /* visible */, false /* hasDirectActivity */, null /* decorSurface */); + + mSplitController.onTaskFragmentParentInfoChanged(mTransaction, TASK_ID, parentInfo); + + // The parent info must be applied to the task container + verify(taskContainer).updateTaskFragmentParentInfo(parentInfo); + verify(mSplitController, never()).updateContainer(any(), any()); + + assertWithMessage("The overlay container must still be dismissed even if " + + "#updateContainer is not called") + .that(taskContainer.getOverlayContainer()).isNull(); + } + /** * A simplified version of {@link SplitController.ActivityStartMonitor * #createOrUpdateOverlayTaskFragmentIfNeeded} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java index 1c94625ddde9..54e162bba2f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java @@ -54,6 +54,7 @@ public class TvPipNotificationController implements TvPipActionsProvider.Listene // Referenced in com.android.systemui.util.NotificationChannels. public static final String NOTIFICATION_CHANNEL = "TVPIP"; private static final String NOTIFICATION_TAG = "TvPip"; + private static final String EXTRA_COMPONENT_NAME = "TvPipComponentName"; private final Context mContext; private final PackageManager mPackageManager; @@ -176,6 +177,7 @@ public class TvPipNotificationController implements TvPipActionsProvider.Listene Bundle extras = new Bundle(); extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken); + extras.putParcelable(EXTRA_COMPONENT_NAME, PipUtils.getTopPipActivity(mContext).first); mNotificationBuilder.setExtras(extras); PendingIntent closeIntent = mTvPipActionsProvider.getCloseAction().getPendingIntent(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index bf783e6af36f..8c2203ef7a49 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -21,17 +21,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING; import static android.view.WindowManager.TRANSIT_PIP; -import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; -import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; -import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; -import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; -import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; -import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.util.TransitionUtil.isOpeningType; import android.annotation.NonNull; @@ -56,7 +48,6 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentsTransitionHandler; -import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.sysui.ShellInit; @@ -84,7 +75,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, private UnfoldTransitionHandler mUnfoldHandler; private ActivityEmbeddingController mActivityEmbeddingController; - private static class MixedTransition { + abstract static class MixedTransition { static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; /** Both the display and split-state (enter/exit) is changing */ @@ -124,15 +115,11 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, int mAnimType = ANIM_TYPE_DEFAULT; final IBinder mTransition; - private final Transitions mPlayer; - private final DefaultMixedHandler mMixedHandler; - private final PipTransitionController mPipHandler; - private final RecentsTransitionHandler mRecentsHandler; - private final StageCoordinator mSplitHandler; - private final KeyguardTransitionHandler mKeyguardHandler; - private final DesktopTasksController mDesktopTasksController; - private final UnfoldTransitionHandler mUnfoldHandler; - private final ActivityEmbeddingController mActivityEmbeddingController; + protected final Transitions mPlayer; + protected final DefaultMixedHandler mMixedHandler; + protected final PipTransitionController mPipHandler; + protected final StageCoordinator mSplitHandler; + protected final KeyguardTransitionHandler mKeyguardHandler; Transitions.TransitionHandler mLeftoversHandler = null; TransitionInfo mInfo = null; @@ -156,409 +143,33 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, MixedTransition(int type, IBinder transition, Transitions player, DefaultMixedHandler mixedHandler, PipTransitionController pipHandler, - RecentsTransitionHandler recentsHandler, StageCoordinator splitHandler, - KeyguardTransitionHandler keyguardHandler, - DesktopTasksController desktopTasksController, - UnfoldTransitionHandler unfoldHandler, - ActivityEmbeddingController activityEmbeddingController) { + StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler) { mType = type; mTransition = transition; mPlayer = player; mMixedHandler = mixedHandler; mPipHandler = pipHandler; - mRecentsHandler = recentsHandler; mSplitHandler = splitHandler; mKeyguardHandler = keyguardHandler; - mDesktopTasksController = desktopTasksController; - mUnfoldHandler = unfoldHandler; - mActivityEmbeddingController = activityEmbeddingController; - - switch (type) { - case TYPE_RECENTS_DURING_DESKTOP: - case TYPE_RECENTS_DURING_KEYGUARD: - case TYPE_RECENTS_DURING_SPLIT: - mLeftoversHandler = mRecentsHandler; - break; - case TYPE_UNFOLD: - mLeftoversHandler = mUnfoldHandler; - break; - case TYPE_DISPLAY_AND_SPLIT_CHANGE: - case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: - case TYPE_ENTER_PIP_FROM_SPLIT: - case TYPE_KEYGUARD: - case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: - default: - break; - } } - boolean startAnimation( + abstract boolean startAnimation( @NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - switch (mType) { - case TYPE_ENTER_PIP_FROM_SPLIT: - return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction, - finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler); - case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: - return animateEnterPipFromActivityEmbedding( - info, startTransaction, finishTransaction, finishCallback); - case TYPE_DISPLAY_AND_SPLIT_CHANGE: - return false; - case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: - final boolean handledToPip = animateOpenIntentWithRemoteAndPip( - info, startTransaction, finishTransaction, finishCallback); - // Consume the transition on remote handler if the leftover handler already - // handle this transition. And if it cannot, the transition will be handled by - // remote handler, so don't consume here. - // Need to check leftOverHandler as it may change in - // #animateOpenIntentWithRemoteAndPip - if (handledToPip && mHasRequestToRemote - && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) { - mPlayer.getRemoteTransitionHandler().onTransitionConsumed( - transition, false, null); - } - return handledToPip; - case TYPE_RECENTS_DURING_SPLIT: - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - // Pip auto-entering info might be appended to recent transition like - // pressing home-key in 3-button navigation. This offers split handler the - // opportunity to handle split to pip animation. - if (mPipHandler.isEnteringPip(change, info.getType()) - && mSplitHandler.getSplitItemPosition(change.getLastParent()) - != SPLIT_POSITION_UNDEFINED) { - return animateEnterPipFromSplit( - this, info, startTransaction, finishTransaction, finishCallback, - mPlayer, mMixedHandler, mPipHandler, mSplitHandler); - } - } - - return animateRecentsDuringSplit( - info, startTransaction, finishTransaction, finishCallback); - case TYPE_KEYGUARD: - return animateKeyguard(this, info, startTransaction, finishTransaction, - finishCallback, mKeyguardHandler, mPipHandler); - case TYPE_RECENTS_DURING_KEYGUARD: - return animateRecentsDuringKeyguard( - info, startTransaction, finishTransaction, finishCallback); - case TYPE_RECENTS_DURING_DESKTOP: - return animateRecentsDuringDesktop( - info, startTransaction, finishTransaction, finishCallback); - case TYPE_UNFOLD: - return animateUnfold( - info, startTransaction, finishTransaction, finishCallback); - default: - throw new IllegalStateException( - "Starting mixed animation without a known mixed type? " + mType); - } - } - - private boolean animateEnterPipFromActivityEmbedding( - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " - + "entering PIP from an Activity Embedding window"); - // Split into two transitions (wct) - TransitionInfo.Change pipChange = null; - final TransitionInfo everythingElse = - subCopy(info, TRANSIT_TO_BACK, true /* changes */); - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - TransitionInfo.Change change = info.getChanges().get(i); - if (mPipHandler.isEnteringPip(change, info.getType())) { - if (pipChange != null) { - throw new IllegalStateException("More than 1 pip-entering changes in one" - + " transition? " + info); - } - pipChange = change; - // going backwards, so remove-by-index is fine. - everythingElse.getChanges().remove(i); - } - } - - final Transitions.TransitionFinishCallback finishCB = (wct) -> { - --mInFlightSubAnimations; - joinFinishArgs(wct); - if (mInFlightSubAnimations > 0) return; - finishCallback.onTransitionFinished(mFinishWCT); - }; - - if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) { - // Fallback to dispatching to other handlers. - return false; - } - - // PIP window should always be on the highest Z order. - if (pipChange != null) { - mInFlightSubAnimations = 2; - mPipHandler.startEnterAnimation( - pipChange, - startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE), - finishTransaction, - finishCB); - } else { - mInFlightSubAnimations = 1; - } - - mActivityEmbeddingController.startAnimation(mTransition, everythingElse, - startTransaction, finishTransaction, finishCB); - return true; - } + @NonNull Transitions.TransitionFinishCallback finishCallback); - private boolean animateOpenIntentWithRemoteAndPip( - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - TransitionInfo.Change pipChange = null; - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - TransitionInfo.Change change = info.getChanges().get(i); - if (mPipHandler.isEnteringPip(change, info.getType())) { - if (pipChange != null) { - throw new IllegalStateException("More than 1 pip-entering changes in one" - + " transition? " + info); - } - pipChange = change; - info.getChanges().remove(i); - } - } - Transitions.TransitionFinishCallback finishCB = (wct) -> { - --mInFlightSubAnimations; - joinFinishArgs(wct); - if (mInFlightSubAnimations > 0) return; - finishCallback.onTransitionFinished(mFinishWCT); - }; - if (pipChange == null) { - if (mLeftoversHandler != null) { - mInFlightSubAnimations = 1; - if (mLeftoversHandler.startAnimation( - mTransition, info, startTransaction, finishTransaction, finishCB)) { - return true; - } - } - return false; - } - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate" - + " animation because remote-animation likely doesn't support it"); - // Split the transition into 2 parts: the pip part and the rest. - mInFlightSubAnimations = 2; - // make a new startTransaction because pip's startEnterAnimation "consumes" it so - // we need a separate one to send over to launcher. - SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); - - mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB); - - // Dispatch the rest of the transition normally. - if (mLeftoversHandler != null - && mLeftoversHandler.startAnimation( - mTransition, info, startTransaction, finishTransaction, finishCB)) { - return true; - } - mLeftoversHandler = mPlayer.dispatchTransition(mTransition, info, - startTransaction, finishTransaction, finishCB, mMixedHandler); - return true; - } - - private boolean animateRecentsDuringSplit( - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - // Split-screen is only interested in the recents transition finishing (and merging), so - // just wrap finish and start recents animation directly. - Transitions.TransitionFinishCallback finishCB = (wct) -> { - mInFlightSubAnimations = 0; - // If pair-to-pair switching, the post-recents clean-up isn't needed. - wct = wct != null ? wct : new WindowContainerTransaction(); - if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) { - mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction); - } else { - // notify pair-to-pair recents animation finish - mSplitHandler.onRecentsPairToPairAnimationFinish(wct); - } - mSplitHandler.onTransitionAnimationComplete(); - finishCallback.onTransitionFinished(wct); - }; - mInFlightSubAnimations = 1; - mSplitHandler.onRecentsInSplitAnimationStart(info); - final boolean handled = mLeftoversHandler.startAnimation(mTransition, info, - startTransaction, finishTransaction, finishCB); - if (!handled) { - mSplitHandler.onRecentsInSplitAnimationCanceled(); - } - return handled; - } - - private boolean animateRecentsDuringKeyguard( - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - if (mInfo == null) { - mInfo = info; - mFinishT = finishTransaction; - mFinishCB = finishCallback; - } - return startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction); - } - - private boolean animateRecentsDuringDesktop( - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - Transitions.TransitionFinishCallback finishCB = wct -> { - mInFlightSubAnimations--; - if (mInFlightSubAnimations == 0) { - finishCallback.onTransitionFinished(wct); - } - }; - - mInFlightSubAnimations++; - boolean consumed = mRecentsHandler.startAnimation( - mTransition, info, startTransaction, finishTransaction, finishCB); - if (!consumed) { - mInFlightSubAnimations--; - return false; - } - if (mDesktopTasksController != null) { - mDesktopTasksController.syncSurfaceState(info, finishTransaction); - return true; - } - - return false; - } - - private boolean animateUnfold( - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - final Transitions.TransitionFinishCallback finishCB = (wct) -> { - mInFlightSubAnimations--; - if (mInFlightSubAnimations > 0) return; - finishCallback.onTransitionFinished(wct); - }; - mInFlightSubAnimations = 1; - // Sync pip state. - if (mPipHandler != null) { - mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); - } - if (mSplitHandler != null && mSplitHandler.isSplitActive()) { - mSplitHandler.updateSurfaces(startTransaction); - } - return mUnfoldHandler.startAnimation( - mTransition, info, startTransaction, finishTransaction, finishCB); - } - - void mergeAnimation( + abstract void mergeAnimation( @NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - switch (mType) { - case TYPE_DISPLAY_AND_SPLIT_CHANGE: - // queue since no actual animation. - break; - case TYPE_ENTER_PIP_FROM_SPLIT: - if (mAnimType == ANIM_TYPE_GOING_HOME) { - boolean ended = mSplitHandler.end(); - // If split couldn't end (because it is remote), then don't end everything - // else since we have to play out the animation anyways. - if (!ended) return; - mPipHandler.end(); - if (mLeftoversHandler != null) { - mLeftoversHandler.mergeAnimation( - transition, info, t, mergeTarget, finishCallback); - } - } else { - mPipHandler.end(); - } - break; - case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: - mPipHandler.end(); - mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget, - finishCallback); - break; - case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: - mPipHandler.end(); - if (mLeftoversHandler != null) { - mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, - finishCallback); - } - break; - case TYPE_RECENTS_DURING_SPLIT: - if (mSplitHandler.isPendingEnter(transition)) { - // Recents -> enter-split means that we are switching from one pair to - // another pair. - mAnimType = ANIM_TYPE_PAIR_TO_PAIR; - } - mLeftoversHandler.mergeAnimation( - transition, info, t, mergeTarget, finishCallback); - break; - case TYPE_KEYGUARD: - mKeyguardHandler.mergeAnimation( - transition, info, t, mergeTarget, finishCallback); - break; - case TYPE_RECENTS_DURING_KEYGUARD: - if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) { - DefaultMixedHandler.handoverTransitionLeashes(mInfo, info, t, mFinishT); - if (animateKeyguard(this, info, t, mFinishT, mFinishCB, mKeyguardHandler, - mPipHandler)) { - finishCallback.onTransitionFinished(null); - } - } - mLeftoversHandler.mergeAnimation( - transition, info, t, mergeTarget, finishCallback); - break; - case TYPE_RECENTS_DURING_DESKTOP: - mLeftoversHandler.mergeAnimation( - transition, info, t, mergeTarget, finishCallback); - break; - case TYPE_UNFOLD: - mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); - break; - default: - throw new IllegalStateException( - "Playing a mixed transition with unknown type? " + mType); - } - } + @NonNull Transitions.TransitionFinishCallback finishCallback); - void onTransitionConsumed( + abstract void onTransitionConsumed( @NonNull IBinder transition, boolean aborted, - @Nullable SurfaceControl.Transaction finishT) { - switch (mType) { - case TYPE_ENTER_PIP_FROM_SPLIT: - mPipHandler.onTransitionConsumed(transition, aborted, finishT); - break; - case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: - mPipHandler.onTransitionConsumed(transition, aborted, finishT); - mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT); - break; - case TYPE_RECENTS_DURING_SPLIT: - case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: - case TYPE_RECENTS_DURING_DESKTOP: - mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); - break; - case TYPE_KEYGUARD: - mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT); - break; - case TYPE_UNFOLD: - mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT); - break; - default: - break; - } - - if (mHasRequestToRemote) { - mPlayer.getRemoteTransitionHandler().onTransitionConsumed( - transition, aborted, finishT); - } - } + @Nullable SurfaceControl.Transaction finishT); - boolean startSubAnimation(Transitions.TransitionHandler handler, TransitionInfo info, + protected boolean startSubAnimation( + Transitions.TransitionHandler handler, TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { if (mInfo != null) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, @@ -573,7 +184,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return true; } - void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) { + private void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) { mInFlightSubAnimations--; if (mInfo != null) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, @@ -644,7 +255,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, throw new IllegalStateException("Unexpected remote transition in" + "pip-enter-from-split request"); } - mActiveTransitions.add(createMixedTransition( + mActiveTransitions.add(createDefaultMixedTransition( MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition)); WindowContainerTransaction out = new WindowContainerTransaction(); @@ -656,7 +267,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mActivityEmbeddingController != null)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request from an Activity Embedding split"); - mActiveTransitions.add(createMixedTransition( + mActiveTransitions.add(createDefaultMixedTransition( MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition)); // Postpone transition splitting to later. WindowContainerTransaction out = new WindowContainerTransaction(); @@ -675,7 +286,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (handler == null) { return null; } - final MixedTransition mixed = createMixedTransition( + final MixedTransition mixed = createDefaultMixedTransition( MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition); mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); @@ -701,7 +312,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mPlayer.getRemoteTransitionHandler(), new WindowContainerTransaction()); } - final MixedTransition mixed = createMixedTransition( + final MixedTransition mixed = createRecentsMixedTransition( MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition); mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); @@ -710,7 +321,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, final WindowContainerTransaction wct = mUnfoldHandler.handleRequest(transition, request); if (wct != null) { - mActiveTransitions.add(createMixedTransition( + mActiveTransitions.add(createDefaultMixedTransition( MixedTransition.TYPE_UNFOLD, transition)); } return wct; @@ -718,6 +329,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return null; } + private DefaultMixedTransition createDefaultMixedTransition(int type, IBinder transition) { + return new DefaultMixedTransition( + type, transition, mPlayer, this, mPipHandler, mSplitHandler, mKeyguardHandler, + mUnfoldHandler, mActivityEmbeddingController); + } + @Override public Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT) { if (mRecentsHandler != null) { @@ -737,31 +354,30 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, private void setRecentsTransitionDuringSplit(IBinder transition) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " + "Split-Screen is foreground, so treat it as Mixed."); - mActiveTransitions.add(createMixedTransition( + mActiveTransitions.add(createRecentsMixedTransition( MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition)); } private void setRecentsTransitionDuringKeyguard(IBinder transition) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " + "keyguard is visible, so treat it as Mixed."); - mActiveTransitions.add(createMixedTransition( + mActiveTransitions.add(createRecentsMixedTransition( MixedTransition.TYPE_RECENTS_DURING_KEYGUARD, transition)); } private void setRecentsTransitionDuringDesktop(IBinder transition) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " + "desktop mode is active, so treat it as Mixed."); - mActiveTransitions.add(createMixedTransition( + mActiveTransitions.add(createRecentsMixedTransition( MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition)); } - private MixedTransition createMixedTransition(int type, IBinder transition) { - return new MixedTransition(type, transition, mPlayer, this, mPipHandler, mRecentsHandler, - mSplitHandler, mKeyguardHandler, mDesktopTasksController, mUnfoldHandler, - mActivityEmbeddingController); + private MixedTransition createRecentsMixedTransition(int type, IBinder transition) { + return new RecentsMixedTransition(type, transition, mPlayer, this, mPipHandler, + mSplitHandler, mKeyguardHandler, mRecentsHandler, mDesktopTasksController); } - private static TransitionInfo subCopy(@NonNull TransitionInfo info, + static TransitionInfo subCopy(@NonNull TransitionInfo info, @WindowManager.TransitionType int newType, boolean withChanges) { final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0); out.setTrack(info.getTrack()); @@ -778,15 +394,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return out; } - private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) { - return change.getTaskInfo() != null - && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME; - } - - private static boolean isWallpaper(@NonNull TransitionInfo.Change change) { - return (change.getFlags() & FLAG_IS_WALLPAPER) != 0; - } - @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -805,7 +412,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (KeyguardTransitionHandler.handles(info)) { if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) { final MixedTransition keyguardMixed = - createMixedTransition(MixedTransition.TYPE_KEYGUARD, transition); + createDefaultMixedTransition(MixedTransition.TYPE_KEYGUARD, transition); mActiveTransitions.add(keyguardMixed); Transitions.TransitionFinishCallback callback = wct -> { mActiveTransitions.remove(keyguardMixed); @@ -845,117 +452,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return handled; } - private static boolean animateEnterPipFromSplit(@NonNull final MixedTransition mixed, - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback, - @NonNull Transitions player, @NonNull DefaultMixedHandler mixedHandler, - @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " - + "entering PIP while Split-Screen is foreground."); - TransitionInfo.Change pipChange = null; - TransitionInfo.Change wallpaper = null; - final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */); - boolean homeIsOpening = false; - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - TransitionInfo.Change change = info.getChanges().get(i); - if (pipHandler.isEnteringPip(change, info.getType())) { - if (pipChange != null) { - throw new IllegalStateException("More than 1 pip-entering changes in one" - + " transition? " + info); - } - pipChange = change; - // going backwards, so remove-by-index is fine. - everythingElse.getChanges().remove(i); - } else if (isHomeOpening(change)) { - homeIsOpening = true; - } else if (isWallpaper(change)) { - wallpaper = change; - } - } - if (pipChange == null) { - // um, something probably went wrong. - return false; - } - final boolean isGoingHome = homeIsOpening; - Transitions.TransitionFinishCallback finishCB = (wct) -> { - --mixed.mInFlightSubAnimations; - mixed.joinFinishArgs(wct); - if (mixed.mInFlightSubAnimations > 0) return; - if (isGoingHome) { - splitHandler.onTransitionAnimationComplete(); - } - finishCallback.onTransitionFinished(mixed.mFinishWCT); - }; - if (isGoingHome || splitHandler.getSplitItemPosition(pipChange.getLastParent()) - != SPLIT_POSITION_UNDEFINED) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed " - + "since entering-PiP caused us to leave split and return home."); - // We need to split the transition into 2 parts: the pip part (animated by pip) - // and the dismiss-part (animated by launcher). - mixed.mInFlightSubAnimations = 2; - // immediately make the wallpaper visible (so that we don't see it pop-in during - // the time it takes to start recents animation (which is remote). - if (wallpaper != null) { - startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f); - } - // make a new startTransaction because pip's startEnterAnimation "consumes" it so - // we need a separate one to send over to launcher. - SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); - @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED; - if (splitHandler.isSplitScreenVisible()) { - // The non-going home case, we could be pip-ing one of the split stages and keep - // showing the other - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - TransitionInfo.Change change = info.getChanges().get(i); - if (change == pipChange) { - // Ignore the change/task that's going into Pip - continue; - } - @SplitScreen.StageType int splitItemStage = - splitHandler.getSplitItemStage(change.getLastParent()); - if (splitItemStage != STAGE_TYPE_UNDEFINED) { - topStageToKeep = splitItemStage; - break; - } - } - } - // Let split update internal state for dismiss. - splitHandler.prepareDismissAnimation(topStageToKeep, - EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, - finishTransaction); - - // We are trying to accommodate launcher's close animation which can't handle the - // divider-bar, so if split-handler is closing the divider-bar, just hide it and remove - // from transition info. - for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) { - if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR) != 0) { - everythingElse.getChanges().remove(i); - break; - } - } - - pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA); - pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, - finishCB); - // Dispatch the rest of the transition normally. This will most-likely be taken by - // recents or default handler. - mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse, - otherStartT, finishTransaction, finishCB, mixedHandler); - } else { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just " - + "forward animation to Pip-Handler."); - // This happens if the pip-ing activity is in a multi-activity task (and thus a - // new pip task is spawned). In this case, we don't actually exit split so we can - // just let pip transition handle the animation verbatim. - mixed.mInFlightSubAnimations = 1; - pipHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction, - finishCB); - } - return true; - } - private void unlinkMissingParents(TransitionInfo from) { for (int i = 0; i < from.getChanges().size(); ++i) { final TransitionInfo.Change chg = from.getChanges().get(i); @@ -987,15 +483,14 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCallback) { - final MixedTransition mixed = createMixedTransition( + final MixedTransition mixed = createDefaultMixedTransition( MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition); mActiveTransitions.add(mixed); Transitions.TransitionFinishCallback callback = wct -> { mActiveTransitions.remove(mixed); finishCallback.onTransitionFinished(wct); }; - return animateEnterPipFromSplit(mixed, info, startT, finishT, finishCallback, mPlayer, this, - mPipHandler, mSplitHandler); + return mixed.startAnimation(transition, info, startT, finishT, callback); } /** @@ -1018,7 +513,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } if (displayPart.getChanges().isEmpty()) return false; unlinkMissingParents(everythingElse); - final MixedTransition mixed = createMixedTransition( + final MixedTransition mixed = createDefaultMixedTransition( MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition); mActiveTransitions.add(mixed); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change " @@ -1135,7 +630,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, * {@link TransitionInfo} so that it can take over some parts of the animation without * reparenting to new transition roots. */ - private static void handoverTransitionLeashes( + static void handoverTransitionLeashes( @NonNull TransitionInfo from, @NonNull TransitionInfo to, @NonNull SurfaceControl.Transaction startT, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java new file mode 100644 index 000000000000..9ce46d69815b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2023 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.wm.shell.transition; + +import static android.view.WindowManager.TRANSIT_TO_BACK; + +import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy; +import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit; +import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.TransitionInfo; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.activityembedding.ActivityEmbeddingController; +import com.android.wm.shell.keyguard.KeyguardTransitionHandler; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.splitscreen.StageCoordinator; +import com.android.wm.shell.unfold.UnfoldTransitionHandler; + +class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { + private final UnfoldTransitionHandler mUnfoldHandler; + private final ActivityEmbeddingController mActivityEmbeddingController; + + DefaultMixedTransition(int type, IBinder transition, Transitions player, + DefaultMixedHandler mixedHandler, PipTransitionController pipHandler, + StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler, + UnfoldTransitionHandler unfoldHandler, + ActivityEmbeddingController activityEmbeddingController) { + super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler); + mUnfoldHandler = unfoldHandler; + mActivityEmbeddingController = activityEmbeddingController; + + switch (type) { + case TYPE_UNFOLD: + mLeftoversHandler = mUnfoldHandler; + break; + case TYPE_DISPLAY_AND_SPLIT_CHANGE: + case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: + case TYPE_ENTER_PIP_FROM_SPLIT: + case TYPE_KEYGUARD: + case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: + default: + break; + } + } + + @Override + boolean startAnimation( + @NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + return switch (mType) { + case TYPE_DISPLAY_AND_SPLIT_CHANGE -> false; + case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING -> + animateEnterPipFromActivityEmbedding( + info, startTransaction, finishTransaction, finishCallback); + case TYPE_ENTER_PIP_FROM_SPLIT -> + animateEnterPipFromSplit(this, info, startTransaction, finishTransaction, + finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler); + case TYPE_KEYGUARD -> + animateKeyguard(this, info, startTransaction, finishTransaction, finishCallback, + mKeyguardHandler, mPipHandler); + case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE -> + animateOpenIntentWithRemoteAndPip(transition, info, startTransaction, + finishTransaction, finishCallback); + case TYPE_UNFOLD -> + animateUnfold(info, startTransaction, finishTransaction, finishCallback); + default -> throw new IllegalStateException( + "Starting default mixed animation with unknown or illegal type: " + mType); + }; + } + + private boolean animateEnterPipFromActivityEmbedding( + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " + + "entering PIP from an Activity Embedding window"); + // Split into two transitions (wct) + TransitionInfo.Change pipChange = null; + final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (mPipHandler.isEnteringPip(change, info.getType())) { + if (pipChange != null) { + throw new IllegalStateException("More than 1 pip-entering changes in one" + + " transition? " + info); + } + pipChange = change; + // going backwards, so remove-by-index is fine. + everythingElse.getChanges().remove(i); + } + } + + final Transitions.TransitionFinishCallback finishCB = (wct) -> { + --mInFlightSubAnimations; + joinFinishArgs(wct); + if (mInFlightSubAnimations > 0) return; + finishCallback.onTransitionFinished(mFinishWCT); + }; + + if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) { + // Fallback to dispatching to other handlers. + return false; + } + + // PIP window should always be on the highest Z order. + if (pipChange != null) { + mInFlightSubAnimations = 2; + mPipHandler.startEnterAnimation( + pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE), + finishTransaction, + finishCB); + } else { + mInFlightSubAnimations = 1; + } + + mActivityEmbeddingController.startAnimation( + mTransition, everythingElse, startTransaction, finishTransaction, finishCB); + return true; + } + + private boolean animateOpenIntentWithRemoteAndPip( + @NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + boolean handledToPip = tryAnimateOpenIntentWithRemoteAndPip( + info, startTransaction, finishTransaction, finishCallback); + // Consume the transition on remote handler if the leftover handler already handle this + // transition. And if it cannot, the transition will be handled by remote handler, so don't + // consume here. + // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip + if (handledToPip && mHasRequestToRemote + && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) { + mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null); + } + return handledToPip; + } + + private boolean tryAnimateOpenIntentWithRemoteAndPip( + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + TransitionInfo.Change pipChange = null; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (mPipHandler.isEnteringPip(change, info.getType())) { + if (pipChange != null) { + throw new IllegalStateException("More than 1 pip-entering changes in one" + + " transition? " + info); + } + pipChange = change; + info.getChanges().remove(i); + } + } + Transitions.TransitionFinishCallback finishCB = (wct) -> { + --mInFlightSubAnimations; + joinFinishArgs(wct); + if (mInFlightSubAnimations > 0) return; + finishCallback.onTransitionFinished(mFinishWCT); + }; + if (pipChange == null) { + if (mLeftoversHandler != null) { + mInFlightSubAnimations = 1; + if (mLeftoversHandler.startAnimation( + mTransition, info, startTransaction, finishTransaction, finishCB)) { + return true; + } + } + return false; + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate" + + " animation because remote-animation likely doesn't support it"); + // Split the transition into 2 parts: the pip part and the rest. + mInFlightSubAnimations = 2; + // make a new startTransaction because pip's startEnterAnimation "consumes" it so + // we need a separate one to send over to launcher. + SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); + + mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB); + + // Dispatch the rest of the transition normally. + if (mLeftoversHandler != null + && mLeftoversHandler.startAnimation(mTransition, info, + startTransaction, finishTransaction, finishCB)) { + return true; + } + mLeftoversHandler = mPlayer.dispatchTransition( + mTransition, info, startTransaction, finishTransaction, finishCB, mMixedHandler); + return true; + } + + private boolean animateUnfold( + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + final Transitions.TransitionFinishCallback finishCB = (wct) -> { + mInFlightSubAnimations--; + if (mInFlightSubAnimations > 0) return; + finishCallback.onTransitionFinished(wct); + }; + mInFlightSubAnimations = 1; + // Sync pip state. + if (mPipHandler != null) { + mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); + } + if (mSplitHandler != null && mSplitHandler.isSplitActive()) { + mSplitHandler.updateSurfaces(startTransaction); + } + return mUnfoldHandler.startAnimation( + mTransition, info, startTransaction, finishTransaction, finishCB); + } + + @Override + void mergeAnimation( + @NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + switch (mType) { + case TYPE_DISPLAY_AND_SPLIT_CHANGE: + // queue since no actual animation. + return; + case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: + mPipHandler.end(); + mActivityEmbeddingController.mergeAnimation( + transition, info, t, mergeTarget, finishCallback); + return; + case TYPE_ENTER_PIP_FROM_SPLIT: + if (mAnimType == ANIM_TYPE_GOING_HOME) { + boolean ended = mSplitHandler.end(); + // If split couldn't end (because it is remote), then don't end everything else + // since we have to play out the animation anyways. + if (!ended) return; + mPipHandler.end(); + if (mLeftoversHandler != null) { + mLeftoversHandler.mergeAnimation( + transition, info, t, mergeTarget, finishCallback); + } + } else { + mPipHandler.end(); + } + return; + case TYPE_KEYGUARD: + mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + return; + case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: + mPipHandler.end(); + if (mLeftoversHandler != null) { + mLeftoversHandler.mergeAnimation( + transition, info, t, mergeTarget, finishCallback); + } + return; + case TYPE_UNFOLD: + mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + return; + default: + throw new IllegalStateException("Playing a default mixed transition with unknown or" + + " illegal type: " + mType); + } + } + + @Override + void onTransitionConsumed( + @NonNull IBinder transition, boolean aborted, + @Nullable SurfaceControl.Transaction finishT) { + switch (mType) { + case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: + mPipHandler.onTransitionConsumed(transition, aborted, finishT); + mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT); + break; + case TYPE_ENTER_PIP_FROM_SPLIT: + mPipHandler.onTransitionConsumed(transition, aborted, finishT); + break; + case TYPE_KEYGUARD: + mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT); + break; + case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: + mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); + break; + case TYPE_UNFOLD: + mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT); + break; + default: + break; + } + + if (mHasRequestToRemote) { + mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java new file mode 100644 index 000000000000..0974cd13f249 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2023 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.wm.shell.transition; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; + +import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; +import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy; + +import android.annotation.NonNull; +import android.view.SurfaceControl; +import android.window.TransitionInfo; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.keyguard.KeyguardTransitionHandler; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.splitscreen.SplitScreen; +import com.android.wm.shell.splitscreen.StageCoordinator; + +public class MixedTransitionHelper { + static boolean animateEnterPipFromSplit( + @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull Transitions player, @NonNull DefaultMixedHandler mixedHandler, + @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " + + "entering PIP while Split-Screen is foreground."); + TransitionInfo.Change pipChange = null; + TransitionInfo.Change wallpaper = null; + final TransitionInfo everythingElse = + subCopy(info, TRANSIT_TO_BACK, true /* changes */); + boolean homeIsOpening = false; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (pipHandler.isEnteringPip(change, info.getType())) { + if (pipChange != null) { + throw new IllegalStateException("More than 1 pip-entering changes in one" + + " transition? " + info); + } + pipChange = change; + // going backwards, so remove-by-index is fine. + everythingElse.getChanges().remove(i); + } else if (isHomeOpening(change)) { + homeIsOpening = true; + } else if (isWallpaper(change)) { + wallpaper = change; + } + } + if (pipChange == null) { + // um, something probably went wrong. + return false; + } + final boolean isGoingHome = homeIsOpening; + Transitions.TransitionFinishCallback finishCB = (wct) -> { + --mixed.mInFlightSubAnimations; + mixed.joinFinishArgs(wct); + if (mixed.mInFlightSubAnimations > 0) return; + if (isGoingHome) { + splitHandler.onTransitionAnimationComplete(); + } + finishCallback.onTransitionFinished(mixed.mFinishWCT); + }; + if (isGoingHome || splitHandler.getSplitItemPosition(pipChange.getLastParent()) + != SPLIT_POSITION_UNDEFINED) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed " + + "since entering-PiP caused us to leave split and return home."); + // We need to split the transition into 2 parts: the pip part (animated by pip) + // and the dismiss-part (animated by launcher). + mixed.mInFlightSubAnimations = 2; + // immediately make the wallpaper visible (so that we don't see it pop-in during + // the time it takes to start recents animation (which is remote). + if (wallpaper != null) { + startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f); + } + // make a new startTransaction because pip's startEnterAnimation "consumes" it so + // we need a separate one to send over to launcher. + SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); + @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED; + if (splitHandler.isSplitScreenVisible()) { + // The non-going home case, we could be pip-ing one of the split stages and keep + // showing the other + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change == pipChange) { + // Ignore the change/task that's going into Pip + continue; + } + @SplitScreen.StageType int splitItemStage = + splitHandler.getSplitItemStage(change.getLastParent()); + if (splitItemStage != STAGE_TYPE_UNDEFINED) { + topStageToKeep = splitItemStage; + break; + } + } + } + // Let split update internal state for dismiss. + splitHandler.prepareDismissAnimation(topStageToKeep, + EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, + finishTransaction); + + // We are trying to accommodate launcher's close animation which can't handle the + // divider-bar, so if split-handler is closing the divider-bar, just hide it and + // remove from transition info. + for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) { + if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR) + != 0) { + everythingElse.getChanges().remove(i); + break; + } + } + + pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA); + pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, + finishCB); + // Dispatch the rest of the transition normally. This will most-likely be taken by + // recents or default handler. + mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse, + otherStartT, finishTransaction, finishCB, mixedHandler); + } else { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just " + + "forward animation to Pip-Handler."); + // This happens if the pip-ing activity is in a multi-activity task (and thus a + // new pip task is spawned). In this case, we don't actually exit split so we can + // just let pip transition handle the animation verbatim. + mixed.mInFlightSubAnimations = 1; + pipHandler.startAnimation( + mixed.mTransition, info, startTransaction, finishTransaction, finishCB); + } + return true; + } + + private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) { + return change.getTaskInfo() != null + && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME; + } + + private static boolean isWallpaper(@NonNull TransitionInfo.Change change) { + return (change.getFlags() & FLAG_IS_WALLPAPER) != 0; + } + + static boolean animateKeyguard( + @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull KeyguardTransitionHandler keyguardHandler, + PipTransitionController pipHandler) { + if (mixed.mFinishT == null) { + mixed.mFinishT = finishTransaction; + mixed.mFinishCB = finishCallback; + } + // Sync pip state. + if (pipHandler != null) { + pipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); + } + return mixed.startSubAnimation(keyguardHandler, info, startTransaction, finishTransaction); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java new file mode 100644 index 000000000000..643e0266d7df --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2023 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.wm.shell.transition; + +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING; + +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.transition.DefaultMixedHandler.handoverTransitionLeashes; +import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit; +import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.desktopmode.DesktopTasksController; +import com.android.wm.shell.keyguard.KeyguardTransitionHandler; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.recents.RecentsTransitionHandler; +import com.android.wm.shell.splitscreen.StageCoordinator; + +class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition { + private final RecentsTransitionHandler mRecentsHandler; + private final DesktopTasksController mDesktopTasksController; + + RecentsMixedTransition(int type, IBinder transition, Transitions player, + DefaultMixedHandler mixedHandler, PipTransitionController pipHandler, + StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler, + RecentsTransitionHandler recentsHandler, + DesktopTasksController desktopTasksController) { + super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler); + mRecentsHandler = recentsHandler; + mDesktopTasksController = desktopTasksController; + mLeftoversHandler = mRecentsHandler; + } + + @Override + boolean startAnimation( + @NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + return switch (mType) { + case TYPE_RECENTS_DURING_DESKTOP -> + animateRecentsDuringDesktop( + info, startTransaction, finishTransaction, finishCallback); + case TYPE_RECENTS_DURING_KEYGUARD -> + animateRecentsDuringKeyguard( + info, startTransaction, finishTransaction, finishCallback); + case TYPE_RECENTS_DURING_SPLIT -> + animateRecentsDuringSplit( + info, startTransaction, finishTransaction, finishCallback); + default -> throw new IllegalStateException( + "Starting Recents mixed animation with unknown or illegal type: " + mType); + }; + } + + private boolean animateRecentsDuringDesktop( + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mInfo == null) { + mInfo = info; + mFinishT = finishTransaction; + mFinishCB = finishCallback; + } + Transitions.TransitionFinishCallback finishCB = wct -> { + mInFlightSubAnimations--; + if (mInFlightSubAnimations == 0) { + finishCallback.onTransitionFinished(wct); + } + }; + + mInFlightSubAnimations++; + boolean consumed = mRecentsHandler.startAnimation( + mTransition, info, startTransaction, finishTransaction, finishCB); + if (!consumed) { + mInFlightSubAnimations--; + return false; + } + if (mDesktopTasksController != null) { + mDesktopTasksController.syncSurfaceState(info, finishTransaction); + return true; + } + + return false; + } + + private boolean animateRecentsDuringKeyguard( + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mInfo == null) { + mInfo = info; + mFinishT = finishTransaction; + mFinishCB = finishCallback; + } + return startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction); + } + + private boolean animateRecentsDuringSplit( + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + // Pip auto-entering info might be appended to recent transition like pressing + // home-key in 3-button navigation. This offers split handler the opportunity to + // handle split to pip animation. + if (mPipHandler.isEnteringPip(change, info.getType()) + && mSplitHandler.getSplitItemPosition(change.getLastParent()) + != SPLIT_POSITION_UNDEFINED) { + return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction, + finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler); + } + } + + // Split-screen is only interested in the recents transition finishing (and merging), so + // just wrap finish and start recents animation directly. + Transitions.TransitionFinishCallback finishCB = (wct) -> { + mInFlightSubAnimations = 0; + // If pair-to-pair switching, the post-recents clean-up isn't needed. + wct = wct != null ? wct : new WindowContainerTransaction(); + if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) { + mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction); + } else { + // notify pair-to-pair recents animation finish + mSplitHandler.onRecentsPairToPairAnimationFinish(wct); + } + mSplitHandler.onTransitionAnimationComplete(); + finishCallback.onTransitionFinished(wct); + }; + mInFlightSubAnimations = 1; + mSplitHandler.onRecentsInSplitAnimationStart(info); + final boolean handled = mLeftoversHandler.startAnimation( + mTransition, info, startTransaction, finishTransaction, finishCB); + if (!handled) { + mSplitHandler.onRecentsInSplitAnimationCanceled(); + } + return handled; + } + + @Override + void mergeAnimation( + @NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + switch (mType) { + case TYPE_RECENTS_DURING_DESKTOP: + mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + return; + case TYPE_RECENTS_DURING_KEYGUARD: + if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) { + handoverTransitionLeashes(mInfo, info, t, mFinishT); + if (animateKeyguard( + this, info, t, mFinishT, mFinishCB, mKeyguardHandler, mPipHandler)) { + finishCallback.onTransitionFinished(null); + } + } + mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, + finishCallback); + return; + case TYPE_RECENTS_DURING_SPLIT: + if (mSplitHandler.isPendingEnter(transition)) { + // Recents -> enter-split means that we are switching from one pair to + // another pair. + mAnimType = DefaultMixedHandler.MixedTransition.ANIM_TYPE_PAIR_TO_PAIR; + } + mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + return; + default: + throw new IllegalStateException("Playing a Recents mixed transition with unknown or" + + " illegal type: " + mType); + } + } + + @Override + void onTransitionConsumed( + @NonNull IBinder transition, boolean aborted, + @Nullable SurfaceControl.Transaction finishT) { + switch (mType) { + case TYPE_RECENTS_DURING_DESKTOP: + case TYPE_RECENTS_DURING_SPLIT: + mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); + break; + default: + break; + } + + if (mHasRequestToRemote) { + mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT); + } + } +} diff --git a/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/gui/Surface.h index de1ba00211d3..2573931c8543 100644 --- a/libs/hostgraphics/gui/Surface.h +++ b/libs/hostgraphics/gui/Surface.h @@ -50,6 +50,8 @@ public: virtual int unlockAndPost() { return 0; } virtual int query(int what, int* value) const { return 0; } + virtual void destroy() {} + protected: virtual ~Surface() {} diff --git a/nfc/Android.bp b/nfc/Android.bp index 5d1404a56a9e..87da299464e4 100644 --- a/nfc/Android.bp +++ b/nfc/Android.bp @@ -70,6 +70,10 @@ java_sdk_library { lint: { strict_updatability_linting: true, }, + apex_available: [ + "//apex_available:platform", + "com.android.nfcservices", + ], } filegroup { diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml index 0b274646214a..0764609d66d3 100644 --- a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml +++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml @@ -66,6 +66,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxLines="2" + android:ellipsize="end" android:hyphenationFrequency="normalFast" android:lineBreakWordStyle="phrase" android:textAppearance="?android:attr/textAppearanceListItem"/> diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml index 8bb56ff0a07d..4f1a9102c35c 100644 --- a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml +++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml @@ -66,6 +66,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxLines="2" + android:ellipsize="end" android:textAppearance="?android:attr/textAppearanceListItem"/> <LinearLayout diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml index 965fdcfd6f98..ed9028472fd0 100644 --- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml +++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml @@ -73,6 +73,11 @@ android:authorities="com.android.spa.gallery.debug.provider" android:exported="false"> </provider> - + <activity + android:name="com.android.settingslib.spa.gallery.SpaDialogActivity" + android:excludeFromRecents="true" + android:exported="true" + android:theme="@style/Theme.SpaLib.Dialog"> + </activity> </application> </manifest> diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt new file mode 100644 index 000000000000..8b80fe241639 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024 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.settingslib.spa.gallery + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.WarningAmber +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import com.android.settingslib.spa.framework.common.LogCategory +import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.dialog.getDialogWidth + + +class SpaDialogActivity : ComponentActivity() { + private val spaEnvironment get() = SpaEnvironmentFactory.instance + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK) + setContent { + SettingsTheme { + Content() + } + } + } + + @Composable + fun Content() { + var openAlertDialog by remember { mutableStateOf(false) } + AlertDialog(openAlertDialog) + LaunchedEffect(key1 = Unit) { + openAlertDialog = true + } + } + + @Composable + fun AlertDialog(openAlertDialog: Boolean) { + when { + openAlertDialog -> { + AlertDialogExample( + onDismissRequest = { finish() }, + onConfirmation = { finish() }, + dialogTitle = intent.getStringExtra(DIALOG_TITLE) ?: DIALOG_TITLE, + dialogText = intent.getStringExtra(DIALOG_TEXT) ?: DIALOG_TEXT, + icon = Icons.Default.WarningAmber + ) + } + } + } + + @Composable + fun AlertDialogExample( + onDismissRequest: () -> Unit, + onConfirmation: () -> Unit, + dialogTitle: String, + dialogText: String, + icon: ImageVector, + ) { + AlertDialog( + modifier = Modifier.width(getDialogWidth()), + icon = { + Icon(icon, contentDescription = null) + }, + title = { + Text(text = dialogTitle) + }, + text = { + Text(text = dialogText) + }, + onDismissRequest = { + onDismissRequest() + }, + dismissButton = { + OutlinedButton( + onClick = { + onDismissRequest() + } + ) { + Text(intent.getStringExtra(DISMISS_TEXT) ?: DISMISS_TEXT) + } + }, + confirmButton = { + Button( + onClick = { + onConfirmation() + }, + ) { + Text(intent.getStringExtra(CONFIRM_TEXT) ?: CONFIRM_TEXT) + } + } + ) + } + + companion object { + private const val TAG = "SpaDialogActivity" + private const val DIALOG_TITLE = "dialogTitle" + private const val DIALOG_TEXT = "dialogText" + private const val CONFIRM_TEXT = "confirmText" + private const val DISMISS_TEXT = "dismissText" + } +} diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml index 25846ec2d20b..4b5a9bc88ca0 100644 --- a/packages/SettingsLib/Spa/spa/res/values/themes.xml +++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml @@ -22,4 +22,6 @@ <item name="android:windowActionBar">false</item> <item name="android:windowNoTitle">true</item> </style> + + <style name="Theme.SpaLib.Dialog" parent="Theme.Material3.DayNight.Dialog"/> </resources> diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt index 207c174fcf4a..8ffd799385ba 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt @@ -99,7 +99,7 @@ private fun AlertDialogPresenter.SettingsAlertDialog( } @Composable -private fun getDialogWidth(): Dp { +fun getDialogWidth(): Dp { val configuration = LocalConfiguration.current return configuration.screenWidthDp.dp * when (configuration.orientation) { Configuration.ORIENTATION_LANDSCAPE -> 0.6f diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 5aa2bfc6441a..8835db6b7508 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1079,6 +1079,8 @@ <!-- [CHAR_LIMIT=NONE] Label for battery on main page of settings --> <string name="power_remaining_settings_home_page"><xliff:g id="percentage" example="10%">%1$s</xliff:g> - <xliff:g id="time_string" example="1 hour left based on your usage">%2$s</xliff:g></string> + <!-- [CHAR_LIMIT=NONE] Label for charging on hold on main page of settings --> + <string name="power_charging_on_hold_settings_home_page"><xliff:g id="level">%1$s</xliff:g> - Charging on hold to protect battery</string> <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery discharging --> <string name="power_remaining_duration_only">About <xliff:g id="time_remaining">%1$s</xliff:g> left</string> <!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration --> @@ -1144,6 +1146,8 @@ <string name="battery_info_status_full">Charged</string> <!-- [CHAR_LIMIT=40] Battery Info screen. Value for a status item. A state which device is fully charged --> <string name="battery_info_status_full_charged">Fully Charged</string> + <!-- [CHAR_LIMIT=None] Battery Info screen. Value for a status item. A state which device charging on hold --> + <string name="battery_info_status_charging_on_hold">Charging on hold</string> <!-- Summary for settings preference disabled by administrator [CHAR LIMIT=50] --> <string name="disabled_by_admin_summary_text">Controlled by admin</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index c2be571b444a..fb14a172d76c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -1,6 +1,7 @@ package com.android.settingslib; import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL; +import static android.webkit.Flags.updateServiceV2; import android.annotation.ColorInt; import android.app.admin.DevicePolicyManager; @@ -34,6 +35,7 @@ import android.net.vcn.VcnTransportInfo; import android.net.wifi.WifiInfo; import android.os.BatteryManager; import android.os.Build; +import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; @@ -44,6 +46,9 @@ import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.util.Log; +import android.webkit.IWebViewUpdateService; +import android.webkit.WebViewFactory; +import android.webkit.WebViewProviderInfo; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -65,6 +70,8 @@ import java.util.List; public class Utils { + private static final String TAG = "Utils"; + @VisibleForTesting static final String STORAGE_MANAGER_ENABLED_PROPERTY = "ro.storage_manager.enabled"; @@ -76,6 +83,7 @@ public class Utils { private static String sPermissionControllerPackageName; private static String sServicesSystemSharedLibPackageName; private static String sSharedSystemSharedLibPackageName; + private static String sDefaultWebViewPackageName; static final int[] WIFI_PIE = { com.android.internal.R.drawable.ic_wifi_signal_0, @@ -445,6 +453,7 @@ public class Utils { || packageName.equals(sServicesSystemSharedLibPackageName) || packageName.equals(sSharedSystemSharedLibPackageName) || packageName.equals(PrintManager.PRINT_SPOOLER_PACKAGE_NAME) + || (updateServiceV2() && packageName.equals(getDefaultWebViewPackageName())) || isDeviceProvisioningPackage(resources, packageName); } @@ -459,6 +468,29 @@ public class Utils { } /** + * Fetch the package name of the default WebView provider. + */ + @Nullable + private static String getDefaultWebViewPackageName() { + if (sDefaultWebViewPackageName != null) { + return sDefaultWebViewPackageName; + } + + try { + IWebViewUpdateService service = WebViewFactory.getUpdateService(); + if (service != null) { + WebViewProviderInfo provider = service.getDefaultWebViewPackage(); + if (provider != null) { + sDefaultWebViewPackageName = provider.packageName; + } + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException when trying to fetch default WebView package Name", e); + } + return sDefaultWebViewPackageName; + } + + /** * Returns the Wifi icon resource for a given RSSI level. * * @param level The number of bars to show (0-4) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt index 64388b7653e0..ff054786cf52 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt @@ -73,7 +73,7 @@ internal class SceneGestureHandler( private val positionalThreshold get() = with(layoutImpl.density) { 56.dp.toPx() } - internal var gestureWithPriority: Any? = null + internal var currentSource: Any? = null /** The [UserAction]s associated to the current swipe. */ private var actionUpOrLeft: UserAction? = null @@ -520,20 +520,22 @@ internal class SceneGestureHandler( private class SceneDraggableHandler( private val gestureHandler: SceneGestureHandler, ) : DraggableHandler { + private val source = this + override fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int) { - gestureHandler.gestureWithPriority = this + gestureHandler.currentSource = source gestureHandler.onDragStarted(pointersDown, startedPosition, overSlop) } override fun onDelta(pixels: Float) { - if (gestureHandler.gestureWithPriority == this) { + if (gestureHandler.currentSource == source) { gestureHandler.onDrag(delta = pixels) } } override fun onDragStopped(velocity: Float) { - if (gestureHandler.gestureWithPriority == this) { - gestureHandler.gestureWithPriority = null + if (gestureHandler.currentSource == source) { + gestureHandler.currentSource = null gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true) } } @@ -586,6 +588,8 @@ internal class SceneNestedScrollHandler( return nextScene != null } + val source = this + return PriorityNestedScrollConnection( orientation = orientation, canStartPreScroll = { offsetAvailable, offsetBeforeStart -> @@ -656,7 +660,7 @@ internal class SceneNestedScrollHandler( canContinueScroll = { true }, canScrollOnFling = false, onStart = { offsetAvailable -> - gestureHandler.gestureWithPriority = this + gestureHandler.currentSource = source gestureHandler.onDragStarted( pointersDown = 1, startedPosition = null, @@ -664,7 +668,7 @@ internal class SceneNestedScrollHandler( ) }, onScroll = { offsetAvailable -> - if (gestureHandler.gestureWithPriority != this) { + if (gestureHandler.currentSource != source) { return@PriorityNestedScrollConnection 0f } @@ -675,7 +679,7 @@ internal class SceneNestedScrollHandler( offsetAvailable }, onStop = { velocityAvailable -> - if (gestureHandler.gestureWithPriority != this) { + if (gestureHandler.currentSource != source) { return@PriorityNestedScrollConnection 0f } diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index d1274d49a14d..3b9d92dc3d02 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -30,6 +30,8 @@ import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; import android.hardware.input.VirtualMouseScrollEvent; +import android.hardware.input.VirtualStylusButtonEvent; +import android.hardware.input.VirtualStylusMotionEvent; import android.hardware.input.VirtualTouchEvent; import android.os.Handler; import android.os.IBinder; @@ -71,12 +73,14 @@ class InputController { static final String PHYS_TYPE_MOUSE = "Mouse"; static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen"; static final String PHYS_TYPE_NAVIGATION_TOUCHPAD = "NavigationTouchpad"; + static final String PHYS_TYPE_STYLUS = "Stylus"; @StringDef(prefix = { "PHYS_TYPE_" }, value = { PHYS_TYPE_DPAD, PHYS_TYPE_KEYBOARD, PHYS_TYPE_MOUSE, PHYS_TYPE_TOUCHSCREEN, PHYS_TYPE_NAVIGATION_TOUCHPAD, + PHYS_TYPE_STYLUS, }) @Retention(RetentionPolicy.SOURCE) @interface PhysType { @@ -188,6 +192,16 @@ class InputController { } } + void createStylus(@NonNull String deviceName, int vendorId, int productId, + @NonNull IBinder deviceToken, int displayId, int height, int width) + throws DeviceCreationException { + final String phys = createPhys(PHYS_TYPE_STYLUS); + createDeviceInternal(InputDeviceDescriptor.TYPE_STYLUS, deviceName, vendorId, + productId, deviceToken, displayId, phys, + () -> mNativeWrapper.openUinputStylus(deviceName, vendorId, productId, phys, + height, width)); + } + void unregisterInputDevice(@NonNull IBinder token) { synchronized (mLock) { final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.remove( @@ -410,6 +424,32 @@ class InputController { } } + boolean sendStylusMotionEvent(@NonNull IBinder token, @NonNull VirtualStylusMotionEvent event) { + synchronized (mLock) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { + return false; + } + return mNativeWrapper.writeStylusMotionEvent(inputDeviceDescriptor.getNativePointer(), + event.getToolType(), event.getAction(), event.getX(), event.getY(), + event.getPressure(), event.getTiltX(), event.getTiltY(), + event.getEventTimeNanos()); + } + } + + boolean sendStylusButtonEvent(@NonNull IBinder token, @NonNull VirtualStylusButtonEvent event) { + synchronized (mLock) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { + return false; + } + return mNativeWrapper.writeStylusButtonEvent(inputDeviceDescriptor.getNativePointer(), + event.getButtonCode(), event.getAction(), event.getEventTimeNanos()); + } + } + public void dump(@NonNull PrintWriter fout) { fout.println(" InputController: "); synchronized (mLock) { @@ -437,7 +477,7 @@ class InputController { } } - @VisibleForTesting + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) Map<IBinder, InputDeviceDescriptor> getInputDeviceDescriptors() { final Map<IBinder, InputDeviceDescriptor> inputDeviceDescriptors = new ArrayMap<>(); synchronized (mLock) { @@ -454,6 +494,8 @@ class InputController { String phys); private static native long nativeOpenUinputTouchscreen(String deviceName, int vendorId, int productId, String phys, int height, int width); + private static native long nativeOpenUinputStylus(String deviceName, int vendorId, + int productId, String phys, int height, int width); private static native void nativeCloseUinput(long ptr); private static native boolean nativeWriteDpadKeyEvent(long ptr, int androidKeyCode, int action, long eventTimeNanos); @@ -468,6 +510,10 @@ class InputController { float relativeY, long eventTimeNanos); private static native boolean nativeWriteScrollEvent(long ptr, float xAxisMovement, float yAxisMovement, long eventTimeNanos); + private static native boolean nativeWriteStylusMotionEvent(long ptr, int toolType, int action, + int locationX, int locationY, int pressure, int tiltX, int tiltY, long eventTimeNanos); + private static native boolean nativeWriteStylusButtonEvent(long ptr, int buttonCode, int action, + long eventTimeNanos); /** Wrapper around the static native methods for tests. */ @VisibleForTesting @@ -491,6 +537,11 @@ class InputController { width); } + public long openUinputStylus(String deviceName, int vendorId, int productId, String phys, + int height, int width) { + return nativeOpenUinputStylus(deviceName, vendorId, productId, phys, height, width); + } + public void closeUinput(long ptr) { nativeCloseUinput(ptr); } @@ -527,21 +578,35 @@ class InputController { long eventTimeNanos) { return nativeWriteScrollEvent(ptr, xAxisMovement, yAxisMovement, eventTimeNanos); } + + public boolean writeStylusMotionEvent(long ptr, int toolType, int action, int locationX, + int locationY, int pressure, int tiltX, int tiltY, long eventTimeNanos) { + return nativeWriteStylusMotionEvent(ptr, toolType, action, locationX, locationY, + pressure, tiltX, tiltY, eventTimeNanos); + } + + public boolean writeStylusButtonEvent(long ptr, int buttonCode, int action, + long eventTimeNanos) { + return nativeWriteStylusButtonEvent(ptr, buttonCode, action, eventTimeNanos); + } } - @VisibleForTesting static final class InputDeviceDescriptor { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + static final class InputDeviceDescriptor { static final int TYPE_KEYBOARD = 1; static final int TYPE_MOUSE = 2; static final int TYPE_TOUCHSCREEN = 3; static final int TYPE_DPAD = 4; static final int TYPE_NAVIGATION_TOUCHPAD = 5; + static final int TYPE_STYLUS = 6; @IntDef(prefix = { "TYPE_" }, value = { TYPE_KEYBOARD, TYPE_MOUSE, TYPE_TOUCHSCREEN, TYPE_DPAD, TYPE_NAVIGATION_TOUCHPAD, + TYPE_STYLUS, }) @Retention(RetentionPolicy.SOURCE) @interface Type { diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 44c3a8d7537f..c8be6b5c8144 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -75,6 +75,9 @@ import android.hardware.input.VirtualMouseConfig; import android.hardware.input.VirtualMouseRelativeEvent; import android.hardware.input.VirtualMouseScrollEvent; import android.hardware.input.VirtualNavigationTouchpadConfig; +import android.hardware.input.VirtualStylusButtonEvent; +import android.hardware.input.VirtualStylusConfig; +import android.hardware.input.VirtualStylusMotionEvent; import android.hardware.input.VirtualTouchEvent; import android.hardware.input.VirtualTouchscreenConfig; import android.os.Binder; @@ -776,6 +779,26 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @Override // Binder call @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void createVirtualStylus(@NonNull VirtualStylusConfig config, + @NonNull IBinder deviceToken) { + super.createVirtualStylus_enforcePermission(); + Objects.requireNonNull(config); + Objects.requireNonNull(deviceToken); + checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId()); + final long ident = Binder.clearCallingIdentity(); + try { + mInputController.createStylus(config.getInputDeviceName(), config.getVendorId(), + config.getProductId(), deviceToken, config.getAssociatedDisplayId(), + config.getHeight(), config.getWidth()); + } catch (InputController.DeviceCreationException e) { + throw new IllegalArgumentException(e); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterInputDevice(IBinder token) { super.unregisterInputDevice_enforcePermission(); final long ident = Binder.clearCallingIdentity(); @@ -881,6 +904,36 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @Override // Binder call @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public boolean sendStylusMotionEvent(@NonNull IBinder token, + @NonNull VirtualStylusMotionEvent event) { + super.sendStylusMotionEvent_enforcePermission(); + Objects.requireNonNull(token); + Objects.requireNonNull(event); + final long ident = Binder.clearCallingIdentity(); + try { + return mInputController.sendStylusMotionEvent(token, event); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public boolean sendStylusButtonEvent(@NonNull IBinder token, + @NonNull VirtualStylusButtonEvent event) { + super.sendStylusButtonEvent_enforcePermission(); + Objects.requireNonNull(token); + Objects.requireNonNull(event); + final long ident = Binder.clearCallingIdentity(); + try { + return mInputController.sendStylusButtonEvent(token, event); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean showPointerIcon) { super.setShowPointerIcon_enforcePermission(); final long ident = Binder.clearCallingIdentity(); @@ -1337,6 +1390,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } + boolean isInputDeviceOwnedByVirtualDevice(int inputDeviceId) { + return mInputController.getInputDeviceDescriptors().values().stream().anyMatch( + inputDeviceDescriptor -> inputDeviceDescriptor.getInputDeviceId() == inputDeviceId); + } + void onEnteringPipBlocked(int uid) { // Do nothing. ActivityRecord#checkEnterPictureInPictureState logs that the display does not // support PiP. diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 0d5cdcbe484c..ef61498e16af 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -838,10 +838,11 @@ public class VirtualDeviceManagerService extends SystemService { } @Override - public boolean isDisplayOwnedByAnyVirtualDevice(int displayId) { + public boolean isInputDeviceOwnedByVirtualDevice(int inputDeviceId) { ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot(); for (int i = 0; i < virtualDevicesSnapshot.size(); i++) { - if (virtualDevicesSnapshot.get(i).isDisplayOwnedByVirtualDevice(displayId)) { + if (virtualDevicesSnapshot.get(i) + .isInputDeviceOwnedByVirtualDevice(inputDeviceId)) { return true; } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 0cff8b7e88ed..c90cc3d2a847 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -94,6 +94,8 @@ import static android.os.Process.ROOT_UID; import static android.os.Process.SHELL_UID; import static android.os.Process.SYSTEM_UID; import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY; +import static android.content.flags.Flags.enableBindPackageIsolatedProcess; + import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICE_BG_LAUNCH; import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_DELEGATE; @@ -791,13 +793,15 @@ public final class ActiveServices { static String getProcessNameForService(ServiceInfo sInfo, ComponentName name, String callingPackage, String instanceName, boolean isSdkSandbox, - boolean inSharedIsolatedProcess) { + boolean inSharedIsolatedProcess, boolean inPrivateSharedIsolatedProcess) { if (isSdkSandbox) { // For SDK sandbox, the process name is passed in as the instanceName return instanceName; } - if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) { - // For regular processes, just the name in sInfo + if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0 + || (inPrivateSharedIsolatedProcess && !isDefaultProcessService(sInfo))) { + // For regular processes, or private package-shared isolated processes, just the name + // in sInfo return sInfo.processName; } // Isolated processes remain. @@ -809,6 +813,10 @@ public final class ActiveServices { } } + private static boolean isDefaultProcessService(ServiceInfo serviceInfo) { + return serviceInfo.applicationInfo.processName.equals(serviceInfo.processName); + } + private static void traceInstant(@NonNull String message, @NonNull ServiceRecord service) { if (!Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { return; @@ -864,7 +872,7 @@ public final class ActiveServices { ServiceLookupResult res = retrieveServiceLocked(service, instanceName, isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage, - callingPid, callingUid, userId, true, callerFg, false, false, null, false); + callingPid, callingUid, userId, true, callerFg, false, false, null, false, false); if (res == null) { return null; } @@ -1550,7 +1558,7 @@ public final class ActiveServices { ServiceLookupResult r = retrieveServiceLocked(service, instanceName, isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, null, Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false, - null, false); + null, false, false); if (r != null) { if (r.record != null) { final long origId = Binder.clearCallingIdentity(); @@ -1642,7 +1650,7 @@ public final class ActiveServices { IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) { ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, callingPackage, Binder.getCallingPid(), Binder.getCallingUid(), - UserHandle.getCallingUserId(), false, false, false, false, false); + UserHandle.getCallingUserId(), false, false, false, false, false, false); IBinder ret = null; if (r != null) { @@ -3714,6 +3722,9 @@ public final class ActiveServices { || (flags & Context.BIND_EXTERNAL_SERVICE_LONG) != 0; final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0; final boolean inSharedIsolatedProcess = (flags & Context.BIND_SHARED_ISOLATED_PROCESS) != 0; + final boolean inPrivateSharedIsolatedProcess = + ((flags & Context.BIND_PACKAGE_ISOLATED_PROCESS) != 0) + && enableBindPackageIsolatedProcess(); final boolean matchQuarantined = (flags & Context.BIND_MATCH_QUARANTINED_COMPONENTS) != 0; @@ -3725,7 +3736,7 @@ public final class ActiveServices { isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg, isBindExternal, allowInstant, null /* fgsDelegateOptions */, - inSharedIsolatedProcess, matchQuarantined); + inSharedIsolatedProcess, inPrivateSharedIsolatedProcess, matchQuarantined); if (res == null) { return 0; } @@ -4204,14 +4215,14 @@ public final class ActiveServices { } private ServiceLookupResult retrieveServiceLocked(Intent service, - String instanceName, String resolvedType, String callingPackage, - int callingPid, int callingUid, int userId, - boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal, - boolean allowInstant, boolean inSharedIsolatedProcess) { + String instanceName, String resolvedType, String callingPackage, int callingPid, + int callingUid, int userId, boolean createIfNeeded, boolean callingFromFg, + boolean isBindExternal, boolean allowInstant, boolean inSharedIsolatedProcess, + boolean inPrivateSharedIsolatedProcess) { return retrieveServiceLocked(service, instanceName, false, INVALID_UID, null, resolvedType, callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal, allowInstant, null /* fgsDelegateOptions */, - inSharedIsolatedProcess); + inSharedIsolatedProcess, inPrivateSharedIsolatedProcess); } // TODO(b/265746493): Special case for HotwordDetectionService, @@ -4233,21 +4244,22 @@ public final class ActiveServices { String callingPackage, int callingPid, int callingUid, int userId, boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal, boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions, - boolean inSharedIsolatedProcess) { + boolean inSharedIsolatedProcess, boolean inPrivateSharedIsolatedProcess) { return retrieveServiceLocked(service, instanceName, isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal, allowInstant, fgsDelegateOptions, inSharedIsolatedProcess, - false /* matchQuarantined */); + inPrivateSharedIsolatedProcess, false /* matchQuarantined */); } - private ServiceLookupResult retrieveServiceLocked(Intent service, - String instanceName, boolean isSdkSandboxService, int sdkSandboxClientAppUid, - String sdkSandboxClientAppPackage, String resolvedType, + private ServiceLookupResult retrieveServiceLocked( + Intent service, String instanceName, boolean isSdkSandboxService, + int sdkSandboxClientAppUid, String sdkSandboxClientAppPackage, String resolvedType, String callingPackage, int callingPid, int callingUid, int userId, boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal, boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions, - boolean inSharedIsolatedProcess, boolean matchQuarantined) { + boolean inSharedIsolatedProcess, boolean inPrivateSharedIsolatedProcess, + boolean matchQuarantined) { if (isSdkSandboxService && instanceName == null) { throw new IllegalArgumentException("No instanceName provided for sdk sandbox process"); } @@ -4344,7 +4356,8 @@ public final class ActiveServices { final ServiceRestarter res = new ServiceRestarter(); final String processName = getProcessNameForService(sInfo, cn, callingPackage, null /* instanceName */, false /* isSdkSandbox */, - false /* inSharedIsolatedProcess */); + false /* inSharedIsolatedProcess */, + false /*inPrivateSharedIsolatedProcess*/); r = new ServiceRecord(mAm, cn /* name */, cn /* instanceName */, sInfo.applicationInfo.packageName, sInfo.applicationInfo.uid, filter, sInfo, callingFromFg, res, processName, @@ -4415,6 +4428,10 @@ public final class ActiveServices { throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + className + " is not exported"); } + if (inPrivateSharedIsolatedProcess) { + throw new SecurityException("BIND_PACKAGE_ISOLATED_PROCESS cannot be " + + "applied to an external service."); + } if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) { throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + className + " is not an isolatedProcess"); @@ -4448,20 +4465,30 @@ public final class ActiveServices { throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + name + " is not an externalService"); } - if (inSharedIsolatedProcess) { + if (inSharedIsolatedProcess && inPrivateSharedIsolatedProcess) { + throw new SecurityException("Either BIND_SHARED_ISOLATED_PROCESS or " + + "BIND_PACKAGE_ISOLATED_PROCESS should be set. Not both."); + } + if (inSharedIsolatedProcess || inPrivateSharedIsolatedProcess) { if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) { throw new SecurityException("BIND_SHARED_ISOLATED_PROCESS failed, " + className + " is not an isolatedProcess"); } + } + if (inPrivateSharedIsolatedProcess && isDefaultProcessService(sInfo)) { + throw new SecurityException("BIND_PACKAGE_ISOLATED_PROCESS cannot be used for " + + "services running in the main app process."); + } + if (inSharedIsolatedProcess) { + if (instanceName == null) { + throw new IllegalArgumentException("instanceName must be provided for " + + "binding a service into a shared isolated process."); + } if ((sInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) == 0) { throw new SecurityException("BIND_SHARED_ISOLATED_PROCESS failed, " + className + " has not set the allowSharedIsolatedProcess " + " attribute."); } - if (instanceName == null) { - throw new IllegalArgumentException("instanceName must be provided for " - + "binding a service into a shared isolated process."); - } } if (userId > 0) { if (mAm.isSystemUserOnly(sInfo.flags)) { @@ -4503,11 +4530,13 @@ public final class ActiveServices { = new Intent.FilterComparison(service.cloneFilter()); final ServiceRestarter res = new ServiceRestarter(); String processName = getProcessNameForService(sInfo, name, callingPackage, - instanceName, isSdkSandboxService, inSharedIsolatedProcess); + instanceName, isSdkSandboxService, inSharedIsolatedProcess, + inPrivateSharedIsolatedProcess); r = new ServiceRecord(mAm, className, name, definingPackageName, definingUid, filter, sInfo, callingFromFg, res, processName, sdkSandboxClientAppUid, - sdkSandboxClientAppPackage, inSharedIsolatedProcess); + sdkSandboxClientAppPackage, + (inSharedIsolatedProcess || inPrivateSharedIsolatedProcess)); res.setService(r); smap.mServicesByInstanceName.put(name, r); smap.mServicesByIntent.put(filter, r); @@ -8504,7 +8533,8 @@ public final class ActiveServices { null /* sdkSandboxClientAppPackage */, null /* resolvedType */, callingPackage, callingPid, callingUid, userId, true /* createIfNeeded */, false /* callingFromFg */, false /* isBindExternal */, false /* allowInstant */ , - options, false /* inSharedIsolatedProcess */); + options, false /* inSharedIsolatedProcess */, + false /*inPrivateSharedIsolatedProcess*/); if (res == null || res.record == null) { Slog.d(TAG, "startForegroundServiceDelegateLocked retrieveServiceLocked returns null"); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e583a6cd6b1f..0e8be8cd0f5a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -4830,7 +4830,11 @@ public class ActivityManagerService extends IActivityManager.Stub if (!mConstants.mEnableWaitForFinishAttachApplication) { finishAttachApplicationInner(startSeq, callingUid, pid); } - maybeSendBootCompletedLocked(app); + + // Temporarily disable sending BOOT_COMPLETED to see if this was impacting perf tests + if (false) { + maybeSendBootCompletedLocked(app); + } } catch (Exception e) { // We need kill the process group here. (b/148588589) Slog.wtf(TAG, "Exception thrown during bind of " + app, e); @@ -9858,7 +9862,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override - public void setApplicationStartInfoCompleteListener( + public void addApplicationStartInfoCompleteListener( IApplicationStartInfoCompleteListener listener, int userId) { enforceNotIsolatedCaller("setApplicationStartInfoCompleteListener"); @@ -9873,7 +9877,8 @@ public class ActivityManagerService extends IActivityManager.Stub @Override - public void clearApplicationStartInfoCompleteListener(int userId) { + public void removeApplicationStartInfoCompleteListener( + IApplicationStartInfoCompleteListener listener, int userId) { enforceNotIsolatedCaller("clearApplicationStartInfoCompleteListener"); // For the simplification, we don't support USER_ALL nor USER_CURRENT here. @@ -9882,7 +9887,8 @@ public class ActivityManagerService extends IActivityManager.Stub } final int callingUid = Binder.getCallingUid(); - mProcessList.getAppStartInfoTracker().clearStartInfoCompleteListener(callingUid, true); + mProcessList.getAppStartInfoTracker().removeStartInfoCompleteListener(listener, callingUid, + true); } @Override diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index 82e554e67b7e..b90e5cd11216 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -119,7 +119,7 @@ public final class AppStartInfoTracker { /** UID as key. */ @GuardedBy("mLock") - private final SparseArray<ApplicationStartInfoCompleteCallback> mCallbacks; + private final SparseArray<ArrayList<ApplicationStartInfoCompleteCallback>> mCallbacks; /** * Whether or not we've loaded the historical app process start info from persistent storage. @@ -465,11 +465,18 @@ public final class AppStartInfoTracker { synchronized (mLock) { if (startInfo.getStartupState() == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) { - ApplicationStartInfoCompleteCallback callback = + final List<ApplicationStartInfoCompleteCallback> callbacks = mCallbacks.get(startInfo.getRealUid()); - if (callback != null) { - callback.onApplicationStartInfoComplete(startInfo); + if (callbacks == null) { + return; } + final int size = callbacks.size(); + for (int i = 0; i < size; i++) { + if (callbacks.get(i) != null) { + callbacks.get(i).onApplicationStartInfoComplete(startInfo); + } + } + mCallbacks.remove(startInfo.getRealUid()); } } } @@ -542,7 +549,6 @@ public final class AppStartInfoTracker { } catch (RemoteException e) { /*ignored*/ } - clearStartInfoCompleteListener(mUid, true); } void unlinkToDeath() { @@ -551,7 +557,7 @@ public final class AppStartInfoTracker { @Override public void binderDied() { - clearStartInfoCompleteListener(mUid, false); + removeStartInfoCompleteListener(mCallback, mUid, false); } } @@ -561,22 +567,43 @@ public final class AppStartInfoTracker { if (!mEnabled) { return; } - mCallbacks.put(uid, new ApplicationStartInfoCompleteCallback(listener, uid)); + ArrayList<ApplicationStartInfoCompleteCallback> callbacks = mCallbacks.get(uid); + if (callbacks == null) { + mCallbacks.set(uid, + callbacks = new ArrayList<ApplicationStartInfoCompleteCallback>()); + } + callbacks.add(new ApplicationStartInfoCompleteCallback(listener, uid)); } } - void clearStartInfoCompleteListener(final int uid, boolean unlinkDeathRecipient) { + void removeStartInfoCompleteListener( + final IApplicationStartInfoCompleteListener listener, final int uid, + boolean unlinkDeathRecipient) { synchronized (mLock) { if (!mEnabled) { return; } - if (unlinkDeathRecipient) { - ApplicationStartInfoCompleteCallback callback = mCallbacks.get(uid); - if (callback != null) { - callback.unlinkToDeath(); + final ArrayList<ApplicationStartInfoCompleteCallback> callbacks = mCallbacks.get(uid); + if (callbacks == null) { + return; + } + final int size = callbacks.size(); + int index; + for (index = 0; index < size; index++) { + final ApplicationStartInfoCompleteCallback callback = callbacks.get(index); + if (callback.mCallback == listener) { + if (unlinkDeathRecipient) { + callback.unlinkToDeath(); + } + break; } } - mCallbacks.remove(uid); + if (index < size) { + callbacks.remove(index); + } + if (callbacks.isEmpty()) { + mCallbacks.remove(uid); + } } } diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index 823788f0b249..b17978370bd7 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -137,9 +137,9 @@ public abstract class VirtualDeviceManagerInternal { public abstract boolean isAppRunningOnAnyVirtualDevice(int uid); /** - * Returns true if the {@code displayId} is owned by any virtual device + * @return whether the input device with the given id was created by a virtual device. */ - public abstract boolean isDisplayOwnedByAnyVirtualDevice(int displayId); + public abstract boolean isInputDeviceOwnedByVirtualDevice(int inputDeviceId); /** * Gets the ids of VirtualDisplays owned by a VirtualDevice. diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 8910b6e58432..082776ad6085 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -624,10 +624,10 @@ public class AutomaticBrightnessController { pw.println(" Current mode=" + autoBrightnessModeToString(mCurrentBrightnessMapper.getMode())); - pw.println(); for (int i = 0; i < mBrightnessMappingStrategyMap.size(); i++) { + pw.println(); pw.println(" Mapper for mode " - + autoBrightnessModeToString(mBrightnessMappingStrategyMap.keyAt(i)) + "="); + + autoBrightnessModeToString(mBrightnessMappingStrategyMap.keyAt(i)) + ":"); mBrightnessMappingStrategyMap.valueAt(i).dump(pw, mBrightnessRangeController.getNormalBrightnessMax()); } @@ -1159,7 +1159,7 @@ public class AutomaticBrightnessController { if (mCurrentBrightnessMapper.getMode() == mode) { return; } - Slog.i(TAG, "Switching to mode " + mode); + Slog.i(TAG, "Switching to mode " + autoBrightnessModeToString(mode)); if (mode == AUTO_BRIGHTNESS_MODE_IDLE || mCurrentBrightnessMapper.getMode() == AUTO_BRIGHTNESS_MODE_IDLE) { switchModeAndShortTermModels(mode); diff --git a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java index 544f490913e2..e0bdda511df3 100644 --- a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java +++ b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java @@ -165,8 +165,15 @@ public class DisplayBrightnessMappingConfig { */ public float[] getLuxArray(@AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) { - return mBrightnessLevelsLuxMap.get( + float[] luxArray = mBrightnessLevelsLuxMap.get( autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset)); + if (luxArray != null) { + return luxArray; + } + + // No array for this preset, fall back to the normal preset + return mBrightnessLevelsLuxMap.get(autoBrightnessModeToString(mode) + "_" + + AutoBrightnessSettingName.normal.getRawName()); } /** @@ -184,8 +191,15 @@ public class DisplayBrightnessMappingConfig { */ public float[] getBrightnessArray( @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) { - return mBrightnessLevelsMap.get( + float[] brightnessArray = mBrightnessLevelsMap.get( autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset)); + if (brightnessArray != null) { + return brightnessArray; + } + + // No array for this preset, fall back to the normal preset + return mBrightnessLevelsMap.get(autoBrightnessModeToString(mode) + "_" + + AutoBrightnessSettingName.normal.getRawName()); } @Override diff --git a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java index a30c4d2b5b0b..e80b9451dd14 100644 --- a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java +++ b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java @@ -16,12 +16,19 @@ package com.android.server.display.mode; +import static com.android.internal.util.FrameworkStatsLog.DISPLAY_MODE_DIRECTOR_VOTE_CHANGED; +import static com.android.internal.util.FrameworkStatsLog.DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ACTIVE; +import static com.android.internal.util.FrameworkStatsLog.DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED; +import static com.android.internal.util.FrameworkStatsLog.DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_REMOVED; + import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Trace; import android.util.SparseArray; import android.view.Display; +import com.android.internal.util.FrameworkStatsLog; + /** * The VotesStatsReporter is responsible for collecting and sending Vote related statistics */ @@ -31,42 +38,77 @@ class VotesStatsReporter { private final boolean mIgnoredRenderRate; private final boolean mFrameworkStatsLogReportingEnabled; + private int mLastMinPriorityReported = Vote.MAX_PRIORITY + 1; + public VotesStatsReporter(boolean ignoreRenderRate, boolean refreshRateVotingTelemetryEnabled) { mIgnoredRenderRate = ignoreRenderRate; mFrameworkStatsLogReportingEnabled = refreshRateVotingTelemetryEnabled; } - void reportVoteAdded(int displayId, int priority, @NonNull Vote vote) { + void reportVoteChanged(int displayId, int priority, @Nullable Vote vote) { + if (vote == null) { + reportVoteRemoved(displayId, priority); + } else { + reportVoteAdded(displayId, priority, vote); + } + } + + private void reportVoteAdded(int displayId, int priority, @NonNull Vote vote) { int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate); Trace.traceCounter(Trace.TRACE_TAG_POWER, TAG + "." + displayId + ":" + Vote.priorityToString(priority), maxRefreshRate); - // if ( mFrameworkStatsLogReportingEnabled) { - // FrameworkStatsLog.write(VOTE_CHANGED, displayID, priority, ADDED, maxRefreshRate, -1); - // } + if (mFrameworkStatsLogReportingEnabled) { + FrameworkStatsLog.write( + DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority, + DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED, + maxRefreshRate, -1); + } } - void reportVoteRemoved(int displayId, int priority) { + private void reportVoteRemoved(int displayId, int priority) { Trace.traceCounter(Trace.TRACE_TAG_POWER, TAG + "." + displayId + ":" + Vote.priorityToString(priority), -1); - // if ( mFrameworkStatsLogReportingEnabled) { - // FrameworkStatsLog.write(VOTE_CHANGED, displayID, priority, REMOVED, -1, -1); - // } + if (mFrameworkStatsLogReportingEnabled) { + FrameworkStatsLog.write( + DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority, + DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_REMOVED, -1, -1); + } } void reportVotesActivated(int displayId, int minPriority, @Nullable Display.Mode baseMode, SparseArray<Vote> votes) { -// if (!mFrameworkStatsLogReportingEnabled) { -// return; -// } -// int selectedRefreshRate = baseMode != null ? (int) baseMode.getRefreshRate() : -1; -// for (int priority = minPriority; priority <= Vote.MAX_PRIORITY; priority ++) { -// Vote vote = votes.get(priority); -// if (vote != null) { -// int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate); -// FrameworkStatsLog.write(VOTE_CHANGED, displayId, priority, -// ACTIVE, maxRefreshRate, selectedRefreshRate); -// } -// } + if (!mFrameworkStatsLogReportingEnabled) { + return; + } + int selectedRefreshRate = baseMode != null ? (int) baseMode.getRefreshRate() : -1; + for (int priority = Vote.MIN_PRIORITY; priority <= Vote.MAX_PRIORITY; priority++) { + if (priority < mLastMinPriorityReported && priority < minPriority) { + continue; + } + Vote vote = votes.get(priority); + if (vote == null) { + continue; + } + + // Was previously reported ACTIVE, changed to ADDED + if (priority >= mLastMinPriorityReported && priority < minPriority) { + int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate); + FrameworkStatsLog.write( + DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority, + DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ADDED, + maxRefreshRate, selectedRefreshRate); + } + // Was previously reported ADDED, changed to ACTIVE + if (priority >= minPriority && priority < mLastMinPriorityReported) { + int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate); + FrameworkStatsLog.write( + DISPLAY_MODE_DIRECTOR_VOTE_CHANGED, displayId, priority, + DISPLAY_MODE_DIRECTOR_VOTE_CHANGED__VOTE_STATUS__STATUS_ACTIVE, + maxRefreshRate, selectedRefreshRate); + } + + mLastMinPriorityReported = minPriority; + } } private static int getMaxRefreshRate(@NonNull Vote vote, boolean ignoreRenderRate) { diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java index 7a1f7e9d857c..56c7c18c0a11 100644 --- a/services/core/java/com/android/server/display/mode/VotesStorage.java +++ b/services/core/java/com/android/server/display/mode/VotesStorage.java @@ -117,22 +117,13 @@ class VotesStorage { Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes); } if (changed) { - reportVoteStats(displayId, priority, vote); + if (mVotesStatsReporter != null) { + mVotesStatsReporter.reportVoteChanged(displayId, priority, vote); + } mListener.onChanged(); } } - private void reportVoteStats(int displayId, int priority, @Nullable Vote vote) { - if (mVotesStatsReporter == null) { - return; - } - if (vote == null) { - mVotesStatsReporter.reportVoteRemoved(displayId, priority); - } else { - mVotesStatsReporter.reportVoteAdded(displayId, priority, vote); - } - } - /** dump class values, for debugging */ void dump(@NonNull PrintWriter pw) { SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>(); diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index 8580b9664075..6236e2b933bc 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -76,6 +76,8 @@ import com.android.internal.inputmethod.InputMethodSubtypeHandle; import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.util.XmlUtils; +import com.android.server.LocalServices; +import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.input.KeyboardMetricsCollector.KeyboardConfigurationEvent; import com.android.server.input.KeyboardMetricsCollector.LayoutSelectionCriteria; import com.android.server.inputmethod.InputMethodManagerInternal; @@ -197,7 +199,7 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { final KeyboardIdentifier keyboardIdentifier = new KeyboardIdentifier(inputDevice); KeyboardConfiguration config = mConfiguredKeyboards.get(deviceId); if (config == null) { - config = new KeyboardConfiguration(); + config = new KeyboardConfiguration(deviceId); mConfiguredKeyboards.put(deviceId, config); } @@ -1093,19 +1095,26 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { @MainThread private void maybeUpdateNotification() { - if (mConfiguredKeyboards.size() == 0) { - hideKeyboardLayoutNotification(); - return; - } + List<KeyboardConfiguration> configurations = new ArrayList<>(); for (int i = 0; i < mConfiguredKeyboards.size(); i++) { + int deviceId = mConfiguredKeyboards.keyAt(i); + KeyboardConfiguration config = mConfiguredKeyboards.valueAt(i); + if (isVirtualDevice(deviceId)) { + continue; + } // If we have a keyboard with no selected layouts, we should always show missing // layout notification even if there are other keyboards that are configured properly. - if (!mConfiguredKeyboards.valueAt(i).hasConfiguredLayouts()) { + if (!config.hasConfiguredLayouts()) { showMissingKeyboardLayoutNotification(); return; } + configurations.add(config); } - showConfiguredKeyboardLayoutNotification(); + if (configurations.size() == 0) { + hideKeyboardLayoutNotification(); + return; + } + showConfiguredKeyboardLayoutNotification(configurations); } @MainThread @@ -1185,10 +1194,11 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } @MainThread - private void showConfiguredKeyboardLayoutNotification() { + private void showConfiguredKeyboardLayoutNotification( + List<KeyboardConfiguration> configurations) { final Resources r = mContext.getResources(); - if (mConfiguredKeyboards.size() != 1) { + if (configurations.size() != 1) { showKeyboardLayoutNotification( r.getString(R.string.keyboard_layout_notification_multiple_selected_title), r.getString(R.string.keyboard_layout_notification_multiple_selected_message), @@ -1196,8 +1206,8 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { return; } - final InputDevice inputDevice = getInputDevice(mConfiguredKeyboards.keyAt(0)); - final KeyboardConfiguration config = mConfiguredKeyboards.valueAt(0); + final KeyboardConfiguration config = configurations.get(0); + final InputDevice inputDevice = getInputDevice(config.getDeviceId()); if (inputDevice == null || !config.hasConfiguredLayouts()) { return; } @@ -1356,6 +1366,13 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { return false; } + @VisibleForTesting + public boolean isVirtualDevice(int deviceId) { + VirtualDeviceManagerInternal vdm = LocalServices.getService( + VirtualDeviceManagerInternal.class); + return vdm == null || vdm.isInputDeviceOwnedByVirtualDevice(deviceId); + } + private static int[] getScriptCodes(@Nullable Locale locale) { if (locale == null) { return new int[0]; @@ -1430,11 +1447,22 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { } private static class KeyboardConfiguration { + // If null or empty, it means no layout is configured for the device. And user needs to // manually set up the device. @Nullable private Set<String> mConfiguredLayouts; + private final int mDeviceId; + + private KeyboardConfiguration(int deviceId) { + mDeviceId = deviceId; + } + + private int getDeviceId() { + return mDeviceId; + } + private boolean hasConfiguredLayouts() { return mConfiguredLayouts != null && !mConfiguredLayouts.isEmpty(); } diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 659c36c56047..5d71439e8f46 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -276,7 +276,8 @@ public abstract class ApexManager { * Returns list of {@code packageName} of apks inside the given apex. * @param apexPackageName Package name of the apk container of apex */ - abstract List<String> getApksInApex(String apexPackageName); + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public abstract List<String> getApksInApex(String apexPackageName); /** * Returns the apex module name for the given package name, if the package is an APEX. Otherwise @@ -751,8 +752,9 @@ public abstract class ApexManager { } } + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) @Override - List<String> getApksInApex(String apexPackageName) { + public List<String> getApksInApex(String apexPackageName) { synchronized (mLock) { Preconditions.checkState(mPackageNameToApexModuleName != null, "APEX packages have not been scanned"); diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java index 29782d9b8b88..f4fb1a108663 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java @@ -159,11 +159,28 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { } } + private boolean shouldTriggerRepairLocked() { + if (mCurrentWebViewPackage == null) { + return true; + } + WebViewProviderInfo defaultProvider = getDefaultWebViewPackage(); + if (mCurrentWebViewPackage.packageName.equals(defaultProvider.packageName)) { + List<UserPackage> userPackages = + mSystemInterface.getPackageInfoForProviderAllUsers( + mContext, defaultProvider); + return !isInstalledAndEnabledForAllUsers(userPackages); + } else { + return false; + } + } + @Override public void prepareWebViewInSystemServer() { try { + boolean repairNeeded = true; synchronized (mLock) { mCurrentWebViewPackage = findPreferredWebViewPackage(); + repairNeeded = shouldTriggerRepairLocked(); String userSetting = mSystemInterface.getUserChosenWebViewProvider(mContext); if (userSetting != null && !userSetting.equals(mCurrentWebViewPackage.packageName)) { @@ -177,26 +194,25 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { } onWebViewProviderChanged(mCurrentWebViewPackage); } + + if (repairNeeded) { + // We didn't find a valid WebView implementation. Try explicitly re-enabling the + // default package for all users in case it was disabled, even if we already did the + // one-time migration before. If this actually changes the state, we will see the + // PackageManager broadcast shortly and try again. + WebViewProviderInfo defaultProvider = getDefaultWebViewPackage(); + Slog.w( + TAG, + "No provider available for all users, trying to enable " + + defaultProvider.packageName); + mSystemInterface.enablePackageForAllUsers( + mContext, defaultProvider.packageName, true); + } + } catch (Throwable t) { // Log and discard errors at this stage as we must not crash the system server. Slog.e(TAG, "error preparing webview provider from system server", t); } - - if (getCurrentWebViewPackage() == null) { - // We didn't find a valid WebView implementation. Try explicitly re-enabling the - // fallback package for all users in case it was disabled, even if we already did the - // one-time migration before. If this actually changes the state, we will see the - // PackageManager broadcast shortly and try again. - WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages(); - WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders); - if (fallbackProvider != null) { - Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName); - mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName, - true); - } else { - Slog.e(TAG, "No valid provider and no fallback available."); - } - } } private void startZygoteWhenReady() { @@ -421,42 +437,43 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { /** * Returns either the package info of the WebView provider determined in the following way: - * If the user has chosen a provider then use that if it is valid, - * otherwise use the first package in the webview priority list that is valid. - * + * If the user has chosen a provider then use that if it is valid, enabled and installed + * for all users, otherwise use the default provider. */ private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException { - ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos(); - - String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext); - // If the user has chosen provider, use that (if it's installed and enabled for all // users). - for (ProviderAndPackageInfo providerAndPackage : providers) { - if (providerAndPackage.provider.packageName.equals(userChosenProvider)) { - // userPackages can contain null objects. - List<UserPackage> userPackages = - mSystemInterface.getPackageInfoForProviderAllUsers(mContext, - providerAndPackage.provider); - if (isInstalledAndEnabledForAllUsers(userPackages)) { - return providerAndPackage.packageInfo; + String userChosenPackageName = mSystemInterface.getUserChosenWebViewProvider(mContext); + WebViewProviderInfo userChosenProvider = + getWebViewProviderForPackage(userChosenPackageName); + if (userChosenProvider != null) { + try { + PackageInfo packageInfo = + mSystemInterface.getPackageInfoForProvider(userChosenProvider); + if (validityResult(userChosenProvider, packageInfo) == VALIDITY_OK) { + List<UserPackage> userPackages = + mSystemInterface.getPackageInfoForProviderAllUsers( + mContext, userChosenProvider); + if (isInstalledAndEnabledForAllUsers(userPackages)) { + return packageInfo; + } } + } catch (NameNotFoundException e) { + Slog.w(TAG, "User chosen WebView package (" + userChosenPackageName + + ") not found"); } } - // User did not choose, or the choice failed; use the most stable provider that is - // installed and enabled for all users, and available by default (not through - // user choice). - for (ProviderAndPackageInfo providerAndPackage : providers) { - if (providerAndPackage.provider.availableByDefault) { - // userPackages can contain null objects. - List<UserPackage> userPackages = - mSystemInterface.getPackageInfoForProviderAllUsers(mContext, - providerAndPackage.provider); - if (isInstalledAndEnabledForAllUsers(userPackages)) { - return providerAndPackage.packageInfo; - } + // User did not choose, or the choice failed; return the default provider even if it is not + // installed or enabled for all users. + WebViewProviderInfo defaultProvider = getDefaultWebViewPackage(); + try { + PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(defaultProvider); + if (validityResult(defaultProvider, packageInfo) == VALIDITY_OK) { + return packageInfo; } + } catch (NameNotFoundException e) { + Slog.w(TAG, "Default WebView package (" + defaultProvider.packageName + ") not found"); } // This should never happen during normal operation (only with modified system images). @@ -464,6 +481,16 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { throw new WebViewPackageMissingException("Could not find a loadable WebView package"); } + private WebViewProviderInfo getWebViewProviderForPackage(String packageName) { + WebViewProviderInfo[] allProviders = getWebViewPackages(); + for (int n = 0; n < allProviders.length; n++) { + if (allProviders[n].packageName.equals(packageName)) { + return allProviders[n]; + } + } + return null; + } + /** * Return true iff {@param packageInfos} point to only installed and enabled packages. * The given packages {@param packageInfos} should all be pointing to the same package, but each diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 8aaf76a165ab..2bd49bfa6219 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -1591,7 +1591,6 @@ class BackNavigationController { private static void setLaunchBehind(@NonNull ActivityRecord activity) { if (!activity.isVisibleRequested()) { - activity.setVisibility(true); // The transition could commit the visibility and in the finishing state, that could // skip commitVisibility call in setVisibility cause the activity won't visible here. // Call it again to make sure the activity could be visible while handling the pending @@ -1669,10 +1668,14 @@ class BackNavigationController { ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, " + "triggerBack=%b", backType, triggerBack); - mNavigationMonitor.stopMonitorForRemote(); - mBackAnimationInProgress = false; - mShowWallpaper = false; - mPendingAnimationBuilder = null; + synchronized (mWindowManagerService.mGlobalLock) { + mNavigationMonitor.stopMonitorForRemote(); + mBackAnimationInProgress = false; + mShowWallpaper = false; + // All animation should be done, clear any un-send animation. + mPendingAnimation = null; + mPendingAnimationBuilder = null; + } } static TaskSnapshot getSnapshot(@NonNull WindowContainer w, diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp index 4cd018b0269e..50d48b7d30e7 100644 --- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp +++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp @@ -44,6 +44,7 @@ enum class DeviceType { MOUSE, TOUCHSCREEN, DPAD, + STYLUS, }; static unique_fd invalidFd() { @@ -98,6 +99,24 @@ static unique_fd openUinput(const char* readableName, jint vendorId, jint produc ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR); ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE); ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT); + break; + case DeviceType::STYLUS: + ioctl(fd, UI_SET_EVBIT, EV_ABS); + ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH); + ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS); + ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS2); + ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_PEN); + ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_RUBBER); + ioctl(fd, UI_SET_ABSBIT, ABS_X); + ioctl(fd, UI_SET_ABSBIT, ABS_Y); + ioctl(fd, UI_SET_ABSBIT, ABS_TILT_X); + ioctl(fd, UI_SET_ABSBIT, ABS_TILT_Y); + ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE); + ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT); + break; + default: + ALOGE("Invalid input device type %d", static_cast<int32_t>(deviceType)); + return invalidFd(); } int version; @@ -158,6 +177,47 @@ static unique_fd openUinput(const char* readableName, jint vendorId, jint produc ALOGE("Error creating touchscreen uinput tracking ids: %s", strerror(errno)); return invalidFd(); } + } else if (deviceType == DeviceType::STYLUS) { + uinput_abs_setup xAbsSetup; + xAbsSetup.code = ABS_X; + xAbsSetup.absinfo.maximum = screenWidth - 1; + xAbsSetup.absinfo.minimum = 0; + if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) { + ALOGE("Error creating stylus uinput x axis: %s", strerror(errno)); + return invalidFd(); + } + uinput_abs_setup yAbsSetup; + yAbsSetup.code = ABS_Y; + yAbsSetup.absinfo.maximum = screenHeight - 1; + yAbsSetup.absinfo.minimum = 0; + if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) { + ALOGE("Error creating stylus uinput y axis: %s", strerror(errno)); + return invalidFd(); + } + uinput_abs_setup tiltXAbsSetup; + tiltXAbsSetup.code = ABS_TILT_X; + tiltXAbsSetup.absinfo.maximum = 90; + tiltXAbsSetup.absinfo.minimum = -90; + if (ioctl(fd, UI_ABS_SETUP, &tiltXAbsSetup) != 0) { + ALOGE("Error creating stylus uinput tilt x axis: %s", strerror(errno)); + return invalidFd(); + } + uinput_abs_setup tiltYAbsSetup; + tiltYAbsSetup.code = ABS_TILT_Y; + tiltYAbsSetup.absinfo.maximum = 90; + tiltYAbsSetup.absinfo.minimum = -90; + if (ioctl(fd, UI_ABS_SETUP, &tiltYAbsSetup) != 0) { + ALOGE("Error creating stylus uinput tilt y axis: %s", strerror(errno)); + return invalidFd(); + } + uinput_abs_setup pressureAbsSetup; + pressureAbsSetup.code = ABS_PRESSURE; + pressureAbsSetup.absinfo.maximum = 255; + pressureAbsSetup.absinfo.minimum = 0; + if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno)); + return invalidFd(); + } } if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) { ALOGE("Error creating uinput device: %s", strerror(errno)); @@ -182,6 +242,17 @@ static unique_fd openUinput(const char* readableName, jint vendorId, jint produc fallback.absmax[ABS_MT_TOUCH_MAJOR] = screenWidth - 1; fallback.absmin[ABS_MT_PRESSURE] = 0; fallback.absmax[ABS_MT_PRESSURE] = 255; + } else if (deviceType == DeviceType::STYLUS) { + fallback.absmin[ABS_X] = 0; + fallback.absmax[ABS_X] = screenWidth - 1; + fallback.absmin[ABS_Y] = 0; + fallback.absmax[ABS_Y] = screenHeight - 1; + fallback.absmin[ABS_TILT_X] = -90; + fallback.absmax[ABS_TILT_X] = 90; + fallback.absmin[ABS_TILT_Y] = -90; + fallback.absmax[ABS_TILT_Y] = 90; + fallback.absmin[ABS_PRESSURE] = 0; + fallback.absmax[ABS_PRESSURE] = 255; } if (TEMP_FAILURE_RETRY(write(fd, &fallback, sizeof(fallback))) != sizeof(fallback)) { ALOGE("Error creating uinput device: %s", strerror(errno)); @@ -234,6 +305,13 @@ static jlong nativeOpenUinputTouchscreen(JNIEnv* env, jobject thiz, jstring name return fd.ok() ? reinterpret_cast<jlong>(new VirtualTouchscreen(std::move(fd))) : INVALID_PTR; } +static jlong nativeOpenUinputStylus(JNIEnv* env, jobject thiz, jstring name, jint vendorId, + jint productId, jstring phys, jint height, jint width) { + auto fd = + openUinputJni(env, name, vendorId, productId, phys, DeviceType::STYLUS, height, width); + return fd.ok() ? reinterpret_cast<jlong>(new VirtualStylus(std::move(fd))) : INVALID_PTR; +} + static void nativeCloseUinput(JNIEnv* env, jobject thiz, jlong ptr) { VirtualInputDevice* virtualInputDevice = reinterpret_cast<VirtualInputDevice*>(ptr); delete virtualInputDevice; @@ -287,6 +365,22 @@ static bool nativeWriteScrollEvent(JNIEnv* env, jobject thiz, jlong ptr, jfloat std::chrono::nanoseconds(eventTimeNanos)); } +// Native methods for VirtualStylus +static bool nativeWriteStylusMotionEvent(JNIEnv* env, jobject thiz, jlong ptr, jint toolType, + jint action, jint locationX, jint locationY, jint pressure, + jint tiltX, jint tiltY, jlong eventTimeNanos) { + VirtualStylus* virtualStylus = reinterpret_cast<VirtualStylus*>(ptr); + return virtualStylus->writeMotionEvent(toolType, action, locationX, locationY, pressure, tiltX, + tiltY, std::chrono::nanoseconds(eventTimeNanos)); +} + +static bool nativeWriteStylusButtonEvent(JNIEnv* env, jobject thiz, jlong ptr, jint buttonCode, + jint action, jlong eventTimeNanos) { + VirtualStylus* virtualStylus = reinterpret_cast<VirtualStylus*>(ptr); + return virtualStylus->writeButtonEvent(buttonCode, action, + std::chrono::nanoseconds(eventTimeNanos)); +} + static JNINativeMethod methods[] = { {"nativeOpenUinputDpad", "(Ljava/lang/String;IILjava/lang/String;)J", (void*)nativeOpenUinputDpad}, @@ -296,6 +390,8 @@ static JNINativeMethod methods[] = { (void*)nativeOpenUinputMouse}, {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)J", (void*)nativeOpenUinputTouchscreen}, + {"nativeOpenUinputStylus", "(Ljava/lang/String;IILjava/lang/String;II)J", + (void*)nativeOpenUinputStylus}, {"nativeCloseUinput", "(J)V", (void*)nativeCloseUinput}, {"nativeWriteDpadKeyEvent", "(JIIJ)Z", (void*)nativeWriteDpadKeyEvent}, {"nativeWriteKeyEvent", "(JIIJ)Z", (void*)nativeWriteKeyEvent}, @@ -303,6 +399,8 @@ static JNINativeMethod methods[] = { {"nativeWriteTouchEvent", "(JIIIFFFFJ)Z", (void*)nativeWriteTouchEvent}, {"nativeWriteRelativeEvent", "(JFFJ)Z", (void*)nativeWriteRelativeEvent}, {"nativeWriteScrollEvent", "(JFFJ)Z", (void*)nativeWriteScrollEvent}, + {"nativeWriteStylusMotionEvent", "(JIIIIIIIJ)Z", (void*)nativeWriteStylusMotionEvent}, + {"nativeWriteStylusButtonEvent", "(JIIJ)Z", (void*)nativeWriteStylusButtonEvent}, }; int register_android_server_companion_virtual_InputController(JNIEnv* env) { diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 3cbceec5b9cd..a46916553abc 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -625,6 +625,9 @@ If no mode is specified, the mapping will be used for the default mode. If no setting is specified, the mapping will be used for the normal brightness setting. + + If no mapping is defined for one of the settings, the mapping for the normal setting will be + used as a fallback. --> <xs:complexType name="luxToBrightnessMapping"> <xs:element name="map" type="nonNegativeFloatToFloatMap"> diff --git a/services/foldables/devicestateprovider/proguard.flags b/services/foldables/devicestateprovider/proguard.flags index 069cbc642050..b810cad5217d 100644 --- a/services/foldables/devicestateprovider/proguard.flags +++ b/services/foldables/devicestateprovider/proguard.flags @@ -1 +1 @@ --keep,allowoptimization,allowaccessmodification class com.android.server.policy.TentModeDeviceStatePolicy { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.policy.BookStyleDeviceStatePolicy { *; } diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java new file mode 100644 index 000000000000..d5a3cffd71dd --- /dev/null +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2023 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.policy; + +import static android.hardware.SensorManager.SENSOR_DELAY_NORMAL; +import static android.view.Display.DEFAULT_DISPLAY; + +import static com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen.OUTER; +import static com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle.ANGLE_0; +import static com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle.ANGLE_0_TO_45; +import static com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle.ANGLE_45_TO_90; +import static com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle.ANGLE_90_TO_180; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.util.ArraySet; +import android.view.Display; +import android.view.Surface; + +import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen; +import com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle; +import com.android.server.policy.BookStylePreferredScreenCalculator.StateTransition; +import com.android.server.policy.BookStyleClosedStatePredicate.ConditionSensorListener.SensorSubscription; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.function.Supplier; + +/** + * 'Closed' state predicate that takes into account the posture of the device + * It accepts list of state transitions that control how the device moves between + * device states. + * See {@link BookStyleStateTransitions} for detailed description of the default behavior. + */ +public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceStateProvider>, + DisplayManager.DisplayListener { + + private final BookStylePreferredScreenCalculator mClosedStateCalculator; + private final Handler mHandler = new Handler(); + private final PostureEstimator mPostureEstimator; + private final DisplayManager mDisplayManager; + + /** + * Creates {@link BookStyleClosedStatePredicate}. It is expected that the device has a pair + * of accelerometer sensors (one for each movable part of the device), see parameter + * descriptions for the behaviour when these sensors are not available. + * @param context context that could be used to get system services + * @param updatesListener callback that will be executed whenever the predicate should be + * checked again + * @param leftAccelerometerSensor accelerometer sensor that is located in the half of the + * device that has the outer screen, in case if this sensor is + * not provided, tent/wedge mode will be detected only using + * orientation sensor and screen rotation, so this mode won't + * be accessible by putting the device on a flat surface + * @param rightAccelerometerSensor accelerometer sensor that is located on the opposite side + * across the hinge from the previous accelerometer sensor, + * in case if this sensor is not provided, reverse wedge mode + * won't be detected, so the device will use closed state using + * constant angle when folding + * @param stateTransitions definition of all possible state transitions, see + * {@link BookStyleStateTransitions} for sample and more details + */ + + public BookStyleClosedStatePredicate(@NonNull Context context, + @NonNull ClosedStateUpdatesListener updatesListener, + @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor, + @NonNull List<StateTransition> stateTransitions) { + mDisplayManager = context.getSystemService(DisplayManager.class); + mDisplayManager.registerDisplayListener(this, mHandler); + + mClosedStateCalculator = new BookStylePreferredScreenCalculator(stateTransitions); + + final SensorManager sensorManager = context.getSystemService(SensorManager.class); + final Sensor orientationSensor = sensorManager.getDefaultSensor( + Sensor.TYPE_DEVICE_ORIENTATION); + + mPostureEstimator = new PostureEstimator(mHandler, sensorManager, + leftAccelerometerSensor, rightAccelerometerSensor, orientationSensor, + updatesListener::onClosedStateUpdated); + } + + /** + * Based on the current sensor readings and current state, returns true if the device should use + * 'CLOSED' device state and false if it should not use 'CLOSED' state (e.g. could use half-open + * or open states). + */ + @Override + public boolean test(FoldableDeviceStateProvider foldableDeviceStateProvider) { + final HingeAngle hingeAngle = hingeAngleFromFloat( + foldableDeviceStateProvider.getHingeAngle()); + + mPostureEstimator.onDeviceClosedStatusChanged(hingeAngle == ANGLE_0); + + final PreferredScreen preferredScreen = mClosedStateCalculator. + calculatePreferredScreen(hingeAngle, mPostureEstimator.isLikelyTentOrWedgeMode(), + mPostureEstimator.isLikelyReverseWedgeMode(hingeAngle)); + + return preferredScreen == OUTER; + } + + private HingeAngle hingeAngleFromFloat(float hingeAngle) { + if (hingeAngle == 0f) { + return ANGLE_0; + } else if (hingeAngle < 45f) { + return ANGLE_0_TO_45; + } else if (hingeAngle < 90f) { + return ANGLE_45_TO_90; + } else { + return ANGLE_90_TO_180; + } + } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == DEFAULT_DISPLAY) { + final Display display = mDisplayManager.getDisplay(displayId); + int displayState = display.getState(); + boolean isDisplayOn = displayState == Display.STATE_ON; + mPostureEstimator.onDisplayPowerStatusChanged(isDisplayOn); + mPostureEstimator.onDisplayRotationChanged(display.getRotation()); + } + } + + @Override + public void onDisplayAdded(int displayId) { + + } + + @Override + public void onDisplayRemoved(int displayId) { + + } + + public interface ClosedStateUpdatesListener { + void onClosedStateUpdated(); + } + + /** + * Estimates if the device is going to enter wedge/tent mode based on the sensor data + */ + private static class PostureEstimator implements SensorEventListener { + + + private static final int FLAT_INCLINATION_THRESHOLD_DEGREES = 8; + + /** + * Alpha parameter of the accelerometer low pass filter: the lower the value, the less high + * frequency noise it filter but reduces the latency. + */ + private static final float GRAVITY_VECTOR_LOW_PASS_ALPHA_VALUE = 0.8f; + + + @Nullable + private final Sensor mLeftAccelerometerSensor; + @Nullable + private final Sensor mRightAccelerometerSensor; + private final Sensor mOrientationSensor; + private final Runnable mOnSensorUpdatedListener; + + private final ConditionSensorListener mConditionedSensorListener; + + @Nullable + private float[] mRightGravityVector; + + @Nullable + private float[] mLeftGravityVector; + + @Nullable + private Integer mLastScreenRotation; + + @Nullable + private SensorEvent mLastDeviceOrientationSensorEvent = null; + + private boolean mScreenTurnedOn = false; + private boolean mDeviceClosed = false; + + public PostureEstimator(Handler handler, SensorManager sensorManager, + @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor, + Sensor orientationSensor, Runnable onSensorUpdated) { + mLeftAccelerometerSensor = leftAccelerometerSensor; + mRightAccelerometerSensor = rightAccelerometerSensor; + mOrientationSensor = orientationSensor; + + mOnSensorUpdatedListener = onSensorUpdated; + + final List<SensorSubscription> sensorSubscriptions = new ArrayList<>(); + if (mLeftAccelerometerSensor != null) { + sensorSubscriptions.add(new SensorSubscription( + mLeftAccelerometerSensor, + /* allowedToListen= */ () -> mScreenTurnedOn && !mDeviceClosed, + /* cleanup= */ () -> mLeftGravityVector = null)); + } + + if (mRightAccelerometerSensor != null) { + sensorSubscriptions.add(new SensorSubscription( + mRightAccelerometerSensor, + /* allowedToListen= */ () -> mScreenTurnedOn, + /* cleanup= */ () -> mRightGravityVector = null)); + } + + sensorSubscriptions.add(new SensorSubscription(mOrientationSensor, + /* allowedToListen= */ () -> mScreenTurnedOn, + /* cleanup= */ () -> mLastDeviceOrientationSensorEvent = null)); + + mConditionedSensorListener = new ConditionSensorListener(sensorManager, this, handler, + sensorSubscriptions); + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor == mRightAccelerometerSensor) { + if (mRightGravityVector == null) { + mRightGravityVector = new float[3]; + } + setNewValueWithHighPassFilter(mRightGravityVector, event.values); + + final boolean isRightMostlyFlat = Objects.equals( + isGravityVectorMostlyFlat(mRightGravityVector), Boolean.TRUE); + + if (isRightMostlyFlat) { + // Reset orientation sensor when the device becomes flat + mLastDeviceOrientationSensorEvent = null; + } + } else if (event.sensor == mLeftAccelerometerSensor) { + if (mLeftGravityVector == null) { + mLeftGravityVector = new float[3]; + } + setNewValueWithHighPassFilter(mLeftGravityVector, event.values); + } else if (event.sensor == mOrientationSensor) { + mLastDeviceOrientationSensorEvent = event; + } + + mOnSensorUpdatedListener.run(); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + + } + + private void setNewValueWithHighPassFilter(float[] output, float[] newValues) { + final float alpha = GRAVITY_VECTOR_LOW_PASS_ALPHA_VALUE; + output[0] = alpha * output[0] + (1 - alpha) * newValues[0]; + output[1] = alpha * output[1] + (1 - alpha) * newValues[1]; + output[2] = alpha * output[2] + (1 - alpha) * newValues[2]; + } + + /** + * Returns true if the phone likely in reverse wedge mode (when a foldable phone is lying + * on the outer screen mostly flat to the ground) + */ + public boolean isLikelyReverseWedgeMode(HingeAngle hingeAngle) { + return hingeAngle != ANGLE_0 && Objects.equals( + isGravityVectorMostlyFlat(mLeftGravityVector), Boolean.TRUE); + } + + /** + * Returns true if the phone is likely in tent or wedge mode when unfolding. Tent mode + * is detected by checking if the phone is in seascape position, screen is rotated to + * landscape or seascape, or if the right side of the device is mostly flat. + */ + public boolean isLikelyTentOrWedgeMode() { + boolean isScreenLandscapeOrSeascape = Objects.equals(mLastScreenRotation, + Surface.ROTATION_270) || Objects.equals(mLastScreenRotation, + Surface.ROTATION_90); + if (isScreenLandscapeOrSeascape) { + return true; + } + + boolean isRightMostlyFlat = Objects.equals( + isGravityVectorMostlyFlat(mRightGravityVector), Boolean.TRUE); + if (isRightMostlyFlat) { + return true; + } + + boolean isSensorSeaScape = Objects.equals(getOrientationSensorRotation(), + Surface.ROTATION_270); + if (isSensorSeaScape) { + return true; + } + + return false; + } + + /** + * Returns true if the passed gravity vector implies that the phone is mostly flat (the + * vector is close to be perpendicular to the ground and has a positive Z component). + * Returns null if there is no data from the sensor. + */ + private Boolean isGravityVectorMostlyFlat(@Nullable float[] vector) { + if (vector == null) return null; + if (vector[0] == 0.0f && vector[1] == 0.0f && vector[2] == 0.0f) { + // Likely we haven't received the actual data yet, treat it as no data + return null; + } + + double vectorMagnitude = Math.sqrt( + vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]); + float normalizedGravityZ = (float) (vector[2] / vectorMagnitude); + + final int inclination = (int) Math.round(Math.toDegrees(Math.acos(normalizedGravityZ))); + return inclination < FLAT_INCLINATION_THRESHOLD_DEGREES; + } + + private Integer getOrientationSensorRotation() { + if (mLastDeviceOrientationSensorEvent == null) return null; + return (int) mLastDeviceOrientationSensorEvent.values[0]; + } + + /** + * Called whenever display status changes, we use this signal to start/stop listening + * to sensors when the display is off to save battery. Using display state instead of + * general power state to reduce the time when sensors are on, we don't need to listen + * to the extra sensors when the screen is off. + */ + public void onDisplayPowerStatusChanged(boolean screenTurnedOn) { + mScreenTurnedOn = screenTurnedOn; + mConditionedSensorListener.updateListeningState(); + } + + /** + * Called whenever we display rotation might have been updated + * @param rotation new rotation + */ + public void onDisplayRotationChanged(int rotation) { + mLastScreenRotation = rotation; + } + + /** + * Called whenever foldable device becomes fully closed or opened + */ + public void onDeviceClosedStatusChanged(boolean deviceClosed) { + mDeviceClosed = deviceClosed; + mConditionedSensorListener.updateListeningState(); + } + } + + /** + * Helper class that subscribes or unsubscribes from a sensor based on a condition specified + * in {@link SensorSubscription} + */ + static class ConditionSensorListener { + private final List<SensorSubscription> mSensorSubscriptions; + private final ArraySet<Sensor> mIsListening = new ArraySet<>(); + + private final SensorManager mSensorManager; + private final SensorEventListener mSensorEventListener; + + private final Handler mHandler; + + public ConditionSensorListener(SensorManager sensorManager, + SensorEventListener sensorEventListener, Handler handler, + List<SensorSubscription> sensorSubscriptions) { + mSensorManager = sensorManager; + mSensorEventListener = sensorEventListener; + mSensorSubscriptions = sensorSubscriptions; + mHandler = handler; + } + + /** + * Updates current listening state of the sensor based on the provided conditions + */ + public void updateListeningState() { + for (int i = 0; i < mSensorSubscriptions.size(); i++) { + final SensorSubscription subscription = mSensorSubscriptions.get(i); + final Sensor sensor = subscription.mSensor; + + final boolean shouldBeListening = subscription.mAllowedToListenSupplier.get(); + final boolean isListening = mIsListening.contains(sensor); + final boolean shouldUpdateListening = isListening != shouldBeListening; + + if (shouldUpdateListening) { + if (shouldBeListening) { + mIsListening.add(sensor); + mSensorManager.registerListener(mSensorEventListener, sensor, + SENSOR_DELAY_NORMAL, mHandler); + } else { + mIsListening.remove(sensor); + mSensorManager.unregisterListener(mSensorEventListener, sensor); + subscription.mOnUnsubscribe.run(); + } + } + } + } + + /** + * Represents a configuration of a single sensor subscription + */ + public static class SensorSubscription { + private final Sensor mSensor; + private final Supplier<Boolean> mAllowedToListenSupplier; + private final Runnable mOnUnsubscribe; + + /** + * @param sensor sensor to listen to + * @param allowedToListen return true when it is allowed to listen to the sensor + * @param cleanup a runnable that will be closed just before unsubscribing from the + * sensor + */ + + public SensorSubscription(Sensor sensor, Supplier<Boolean> allowedToListen, + Runnable cleanup) { + mSensor = sensor; + mAllowedToListenSupplier = allowedToListen; + mOnUnsubscribe = cleanup; + } + } + } +} diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java index 5968b6346d35..ad938aff396a 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java @@ -21,10 +21,12 @@ import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_WHEN_REQUES import static com.android.server.devicestate.DeviceState.FLAG_EMULATED_ONLY; import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE; import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL; +import static com.android.server.policy.BookStyleStateTransitions.DEFAULT_STATE_TRANSITIONS; import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig; import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createTentModeClosedState; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorManager; @@ -39,12 +41,15 @@ import com.android.server.policy.feature.flags.FeatureFlagsImpl; import java.util.function.Predicate; /** - * Device state policy for a foldable device that supports tent mode: a mode when the device - * keeps the outer display on until reaching a certain hinge angle threshold. + * Device state policy for a foldable device with two screens in a book style, where the hinge is + * located on the left side of the device when in folded posture. + * The policy supports tent/wedge mode: a mode when the device keeps the outer display on + * until reaching certain conditions like hinge angle threshold. * * Contains configuration for {@link FoldableDeviceStateProvider}. */ -public class TentModeDeviceStatePolicy extends DeviceStatePolicy { +public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements + BookStyleClosedStatePredicate.ClosedStateUpdatesListener { private static final int DEVICE_STATE_CLOSED = 0; private static final int DEVICE_STATE_HALF_OPENED = 1; @@ -57,9 +62,10 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { private static final int MIN_CLOSED_ANGLE_DEGREES = 0; private static final int MAX_CLOSED_ANGLE_DEGREES = 5; - private final DeviceStateProvider mProvider; + private final FoldableDeviceStateProvider mProvider; private final boolean mIsDualDisplayBlockingEnabled; + private final boolean mEnablePostureBasedClosedState; private static final Predicate<FoldableDeviceStateProvider> ALLOWED = p -> true; private static final Predicate<FoldableDeviceStateProvider> NOT_ALLOWED = p -> false; @@ -73,30 +79,30 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { * between folded and unfolded modes, otherwise when folding the * display switch will happen at 0 degrees */ - public TentModeDeviceStatePolicy(@NonNull Context context, - @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor, int closeAngleDegrees) { - this(new FeatureFlagsImpl(), context, hingeAngleSensor, hallSensor, closeAngleDegrees); - } - - public TentModeDeviceStatePolicy(@NonNull FeatureFlags featureFlags, @NonNull Context context, - @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor, - int closeAngleDegrees) { + public BookStyleDeviceStatePolicy(@NonNull FeatureFlags featureFlags, @NonNull Context context, + @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor, + @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor, + Integer closeAngleDegrees) { super(context); final SensorManager sensorManager = mContext.getSystemService(SensorManager.class); final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); - final DeviceStateConfiguration[] configuration = createConfiguration(closeAngleDegrees); - + mEnablePostureBasedClosedState = featureFlags.enableFoldablesPostureBasedClosedState(); mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking(); + final DeviceStateConfiguration[] configuration = createConfiguration( + leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees); + mProvider = new FoldableDeviceStateProvider(mContext, sensorManager, hingeAngleSensor, hallSensor, displayManager, configuration); } - private DeviceStateConfiguration[] createConfiguration(int closeAngleDegrees) { + private DeviceStateConfiguration[] createConfiguration(@Nullable Sensor leftAccelerometerSensor, + @Nullable Sensor rightAccelerometerSensor, Integer closeAngleDegrees) { return new DeviceStateConfiguration[]{ - createClosedConfiguration(closeAngleDegrees), + createClosedConfiguration(leftAccelerometerSensor, rightAccelerometerSensor, + closeAngleDegrees), createConfig(DEVICE_STATE_HALF_OPENED, /* name= */ "HALF_OPENED", /* activeStatePredicate= */ (provider) -> { @@ -123,8 +129,10 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { }; } - private DeviceStateConfiguration createClosedConfiguration(int closeAngleDegrees) { - if (closeAngleDegrees > 0) { + private DeviceStateConfiguration createClosedConfiguration( + @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor, + @Nullable Integer closeAngleDegrees) { + if (closeAngleDegrees != null) { // Switch displays at closeAngleDegrees in both ways (folding and unfolding) return createConfig( DEVICE_STATE_CLOSED, @@ -137,6 +145,19 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { ); } + if (mEnablePostureBasedClosedState) { + // Use smart closed state predicate that will use different switch angles + // based on the device posture (e.g. wedge mode, tent mode, reverse wedge mode) + return createConfig( + DEVICE_STATE_CLOSED, + /* name= */ "CLOSED", + /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS, + /* activeStatePredicate= */ new BookStyleClosedStatePredicate(mContext, + this, leftAccelerometerSensor, rightAccelerometerSensor, + DEFAULT_STATE_TRANSITIONS) + ); + } + // Switch to the outer display only at 0 degrees but use TENT_MODE_SWITCH_ANGLE_DEGREES // angle when switching to the inner display return createTentModeClosedState(DEVICE_STATE_CLOSED, @@ -148,6 +169,11 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { } @Override + public void onClosedStateUpdated() { + mProvider.notifyDeviceStateChangedIfNeeded(); + } + + @Override public DeviceStateProvider getDeviceStateProvider() { return mProvider; } diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java new file mode 100644 index 000000000000..8977422a90a8 --- /dev/null +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStylePreferredScreenCalculator.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2023 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.policy; + +import android.annotation.Nullable; + +import java.util.List; +import java.util.Objects; + +/** + * Calculates if we should use outer or inner display on foldable devices based on a several + * inputs like device orientation, hinge angle signals. + * + * This is a stateful class and acts like a state machine with fixed number of states + * and transitions. It allows to list all possible state transitions instead of performing + * imperative logic to make sure that we cover all scenarios and improve debuggability. + * + * See {@link BookStyleStateTransitions} for detailed description of the default behavior. + */ +public class BookStylePreferredScreenCalculator { + + /** + * When calculating the new state we will re-calculate it until it settles down. We re-calculate + * it because the new state might trigger another state transition and this might happen + * several times. We don't want to have infinite loops in state calculation, so this value + * limits the number of such state transitions. + * For example, in the default configuration {@link BookStyleStateTransitions}, after each + * transition with 'set sticky flag' output it will perform a transition to a state without + * 'set sticky flag' output. + * We also have a unit test covering all possible states which checks that we don't have such + * states that could end up in an infinite transition. See sample test for the default + * transitions in {@link BookStyleClosedStateCalculatorTest}. + */ + private static final int MAX_STATE_CHANGES = 16; + + private State mState = new State( + /* stickyKeepOuterUntil90Degrees= */ false, + /* stickyKeepInnerUntil45Degrees= */ false, + PreferredScreen.INVALID); + + private final List<StateTransition> mStateTransitions; + + /** + * Creates BookStyleClosedStateCalculator + * @param stateTransitions list of all state transitions + */ + public BookStylePreferredScreenCalculator(List<StateTransition> stateTransitions) { + mStateTransitions = stateTransitions; + } + + /** + * Calculates updated {@link PreferredScreen} based on the current inputs and the current state. + * The calculation is done based on defined {@link StateTransition}s, it might perform + * multiple transitions until we settle down on a single state. Multiple transitions could be + * performed in case if {@link StateTransition} causes another update of the state. + * There is a limit of maximum {@link MAX_STATE_CHANGES} state transitions, after which + * this method will throw an {@link IllegalStateException}. + * + * @param angle current hinge angle + * @param likelyTentOrWedge true if the device is likely in tent or wedge mode + * @param likelyReverseWedge true if the device is likely in reverse wedge mode + * @return updated {@link PreferredScreen} + */ + public PreferredScreen calculatePreferredScreen(HingeAngle angle, boolean likelyTentOrWedge, + boolean likelyReverseWedge) { + int attempts = 0; + State newState = calculateNewState(mState, angle, likelyTentOrWedge, likelyReverseWedge); + while (attempts < MAX_STATE_CHANGES && !Objects.equals(mState, newState)) { + mState = newState; + newState = calculateNewState(mState, angle, likelyTentOrWedge, likelyReverseWedge); + attempts++; + } + + if (attempts >= MAX_STATE_CHANGES) { + throw new IllegalStateException( + "Can't settle state " + mState + ", inputs: hingeAngle = " + angle + + ", likelyTentOrWedge = " + likelyTentOrWedge + + ", likelyReverseWedge = " + likelyReverseWedge); + } + + final State oldState = mState; + mState = newState; + + if (mState.mPreferredScreen == PreferredScreen.INVALID) { + throw new IllegalStateException( + "Reached invalid state " + mState + ", inputs: hingeAngle = " + angle + + ", likelyTentOrWedge = " + likelyTentOrWedge + + ", likelyReverseWedge = " + likelyReverseWedge + ", old state: " + + oldState); + } + + return mState.mPreferredScreen; + } + + /** + * Returns the current state of the calculator + */ + public State getState() { + return mState; + } + + private State calculateNewState(State current, HingeAngle hingeAngle, boolean likelyTentOrWedge, + boolean likelyReverseWedge) { + for (int i = 0; i < mStateTransitions.size(); i++) { + final State newState = mStateTransitions.get(i).tryTransition(hingeAngle, + likelyTentOrWedge, likelyReverseWedge, current); + if (newState != null) { + return newState; + } + } + + throw new IllegalArgumentException( + "Entry not found for state: " + current + ", hingeAngle = " + hingeAngle + + ", likelyTentOrWedge = " + likelyTentOrWedge + ", likelyReverseWedge = " + + likelyReverseWedge); + } + + /** + * The angle between two halves of the foldable device in degrees. The angle is '0' when + * the device is fully closed and '180' when the device is fully open and flat. + */ + public enum HingeAngle { + ANGLE_0, + ANGLE_0_TO_45, + ANGLE_45_TO_90, + ANGLE_90_TO_180 + } + + /** + * Resulting closed state of the device, where OPEN state indicates that the device should use + * the inner display and CLOSED means that it should use the outer (cover) screen. + */ + public enum PreferredScreen { + INNER, + OUTER, + INVALID + } + + /** + * Describes a state transition for the posture based active screen calculator + */ + public static class StateTransition { + private final Input mInput; + private final State mOutput; + + public StateTransition(HingeAngle hingeAngle, boolean likelyTentOrWedge, + boolean likelyReverseWedge, + boolean stickyKeepOuterUntil90Degrees, boolean stickyKeepInnerUntil45Degrees, + PreferredScreen preferredScreen, Boolean setStickyKeepOuterUntil90Degrees, + Boolean setStickyKeepInnerUntil45Degrees) { + mInput = new Input(hingeAngle, likelyTentOrWedge, likelyReverseWedge, + stickyKeepOuterUntil90Degrees, stickyKeepInnerUntil45Degrees); + mOutput = new State(setStickyKeepOuterUntil90Degrees, + setStickyKeepInnerUntil45Degrees, preferredScreen); + } + + /** + * Returns true if the state transition is applicable for the given inputs + */ + private boolean isApplicable(HingeAngle hingeAngle, boolean likelyTentOrWedge, + boolean likelyReverseWedge, State currentState) { + return mInput.hingeAngle == hingeAngle + && mInput.likelyTentOrWedge == likelyTentOrWedge + && mInput.likelyReverseWedge == likelyReverseWedge + && Objects.equals(mInput.stickyKeepOuterUntil90Degrees, + currentState.stickyKeepOuterUntil90Degrees) + && Objects.equals(mInput.stickyKeepInnerUntil45Degrees, + currentState.stickyKeepInnerUntil45Degrees); + } + + /** + * Try to perform transition for the inputs, returns new state if this + * transition is applicable for the given state and inputs + */ + @Nullable + State tryTransition(HingeAngle hingeAngle, boolean likelyTentOrWedge, + boolean likelyReverseWedge, State currentState) { + if (!isApplicable(hingeAngle, likelyTentOrWedge, likelyReverseWedge, currentState)) { + return null; + } + + boolean stickyKeepOuterUntil90Degrees = currentState.stickyKeepOuterUntil90Degrees; + boolean stickyKeepInnerUntil45Degrees = currentState.stickyKeepInnerUntil45Degrees; + + if (mOutput.stickyKeepOuterUntil90Degrees != null) { + stickyKeepOuterUntil90Degrees = + mOutput.stickyKeepOuterUntil90Degrees; + } + + if (mOutput.stickyKeepInnerUntil45Degrees != null) { + stickyKeepInnerUntil45Degrees = + mOutput.stickyKeepInnerUntil45Degrees; + } + + return new State(stickyKeepOuterUntil90Degrees, stickyKeepInnerUntil45Degrees, + mOutput.mPreferredScreen); + } + } + + /** + * The input part of the {@link StateTransition}, these are the values that are used + * to decide which {@link State} output to choose. + */ + private static class Input { + final HingeAngle hingeAngle; + final boolean likelyTentOrWedge; + final boolean likelyReverseWedge; + final boolean stickyKeepOuterUntil90Degrees; + final boolean stickyKeepInnerUntil45Degrees; + + public Input(HingeAngle hingeAngle, boolean likelyTentOrWedge, + boolean likelyReverseWedge, + boolean stickyKeepOuterUntil90Degrees, boolean stickyKeepInnerUntil45Degrees) { + this.hingeAngle = hingeAngle; + this.likelyTentOrWedge = likelyTentOrWedge; + this.likelyReverseWedge = likelyReverseWedge; + this.stickyKeepOuterUntil90Degrees = stickyKeepOuterUntil90Degrees; + this.stickyKeepInnerUntil45Degrees = stickyKeepInnerUntil45Degrees; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Input)) return false; + Input that = (Input) o; + return likelyTentOrWedge == that.likelyTentOrWedge + && likelyReverseWedge == that.likelyReverseWedge + && stickyKeepOuterUntil90Degrees == that.stickyKeepOuterUntil90Degrees + && stickyKeepInnerUntil45Degrees == that.stickyKeepInnerUntil45Degrees + && hingeAngle == that.hingeAngle; + } + + @Override + public int hashCode() { + return Objects.hash(hingeAngle, likelyTentOrWedge, likelyReverseWedge, + stickyKeepOuterUntil90Degrees, stickyKeepInnerUntil45Degrees); + } + + @Override + public String toString() { + return "InputState{" + + "hingeAngle=" + hingeAngle + + ", likelyTentOrWedge=" + likelyTentOrWedge + + ", likelyReverseWedge=" + likelyReverseWedge + + ", stickyKeepOuterUntil90Degrees=" + stickyKeepOuterUntil90Degrees + + ", stickyKeepInnerUntil45Degrees=" + stickyKeepInnerUntil45Degrees + + '}'; + } + } + + /** + * Class that holds a state of the calculator, it could be used to store the current + * state or to define the target (output) state based on some input in {@link StateTransition}. + */ + public static class State { + public Boolean stickyKeepOuterUntil90Degrees; + public Boolean stickyKeepInnerUntil45Degrees; + + PreferredScreen mPreferredScreen; + + public State(Boolean stickyKeepOuterUntil90Degrees, + Boolean stickyKeepInnerUntil45Degrees, + PreferredScreen preferredScreen) { + this.stickyKeepOuterUntil90Degrees = stickyKeepOuterUntil90Degrees; + this.stickyKeepInnerUntil45Degrees = stickyKeepInnerUntil45Degrees; + this.mPreferredScreen = preferredScreen; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof State)) return false; + State that = (State) o; + return Objects.equals(stickyKeepOuterUntil90Degrees, + that.stickyKeepOuterUntil90Degrees) && Objects.equals( + stickyKeepInnerUntil45Degrees, that.stickyKeepInnerUntil45Degrees) + && mPreferredScreen == that.mPreferredScreen; + } + + @Override + public int hashCode() { + return Objects.hash(stickyKeepOuterUntil90Degrees, stickyKeepInnerUntil45Degrees, + mPreferredScreen); + } + + @Override + public String toString() { + return "State{" + + "stickyKeepOuterUntil90Degrees=" + stickyKeepOuterUntil90Degrees + + ", stickyKeepInnerUntil90Degrees=" + stickyKeepInnerUntil45Degrees + + ", closedState=" + mPreferredScreen + + '}'; + } + } +} diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleStateTransitions.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleStateTransitions.java new file mode 100644 index 000000000000..16daacb36693 --- /dev/null +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleStateTransitions.java @@ -0,0 +1,722 @@ +/* + * Copyright (C) 2023 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.policy; + +import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen; +import com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle; +import com.android.server.policy.BookStylePreferredScreenCalculator.StateTransition; + +import java.util.ArrayList; +import java.util.List; + +/** + * Describes all possible state transitions for {@link BookStylePreferredScreenCalculator}. + * It contains a default configuration for a foldable device that has two screens: smaller outer + * screen which has portrait natural orientation and a larger inner screen and allows to use the + * device in tent mode or wedge mode. + * + * As the output state could affect calculating of the new state, it could potentially cause + * infinite loop and make the state never settle down. This could be avoided using automated test + * that checks all possible inputs and asserts that the final state is valid. + * See sample test for the default transitions in {@link BookStyleClosedStateCalculatorTest}. + * + * - Tent mode is defined as a posture when the device is partially opened and placed on the ground + * on the edges that are parallel to the hinge. + * - Wedge mode is when the device is partially opened and placed flat on the ground with the part + * of the device that doesn't have the display + * - Reverse wedge mode is when the device is partially opened and placed flat on the ground with + * the outer screen down, so the outer screen is not accessible + * + * Behavior description: + * - When unfolding with screens off we assume that no sensor data available except hinge angle + * (based on hall sensor), so we switch to the inner screen immediately + * + * - When unfolding when screen is 'on' we can check if we are likely in tent or wedge mode + * - If not likely tent/wedge mode or sensors data not available, then we unfold immediately + * After unfolding, the state of the inner screen 'on' is sticky between 0 and 45 degrees, so + * it won't jump back to the outer screen even if you move the phone into tent/wedge mode. The + * stickiness is reset after fully closing the device or unfolding past 45 degrees. + * - If likely tent or wedge mode, switch only at 90 degrees + * Tent/wedge mode is 'sticky' between 0 and 90 degrees, so it won't reset until you either + * fully close the device or unfold past 90 degrees. + * + * - When folding we can check if we are likely in reverse wedge mode + * - If not likely in reverse wedge mode or sensor data is not available we switch to the outer + * screen at 45 degrees and enable sticky tent/wedge mode as before, this allows to enter + * tent/wedge mode even if you are not on an even surface or holding phone in landscape + * - If likely in reverse wedge mode, switch to the outer screen only at 0 degrees to allow + * some use cases like using camera in this posture, the check happens after passing 45 degrees + * and inner screen becomes sticky turned 'on' until fully closing or unfolding past 45 degrees + */ +public class BookStyleStateTransitions { + + public static final List<StateTransition> DEFAULT_STATE_TRANSITIONS = new ArrayList<>(); + + static { + // region Angle 0 + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ true + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ false, + /* setStickyKeepInnerUntil45Degrees */ true + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ true + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ false, + /* setStickyKeepInnerUntil45Degrees */ true + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ false + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ false, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ false + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + // endregion + + // region Angle 0-45 + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ true, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ true + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ true, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ true + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_0_TO_45, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + // endregion + + // region Angle 45-90 + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ false + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ true + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ false + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ true + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.OUTER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_45_TO_90, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + // endregion + + // region Angle 90-180 + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ false + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ false, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ false + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ false, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ false, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ false + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ false, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ false, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ false, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ false + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ false, + PreferredScreen.INNER, + /* setStickyKeepOuterUntil90Degrees */ false, + /* setStickyKeepInnerUntil45Degrees */ null + )); + DEFAULT_STATE_TRANSITIONS.add(new StateTransition( + HingeAngle.ANGLE_90_TO_180, + /* likelyTentOrWedge */ true, + /* likelyReverseWedge */ true, + /* stickyKeepOuterUntil90Degrees */ true, + /* stickyKeepInnerUntil45Degrees */ true, + PreferredScreen.INVALID, + /* setStickyKeepOuterUntil90Degrees */ null, + /* setStickyKeepInnerUntil45Degrees */ null + )); + // endregion + } +} diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java new file mode 100644 index 000000000000..8d01b7a9c523 --- /dev/null +++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java @@ -0,0 +1,703 @@ +/* + * Copyright (C) 2023 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.policy; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.STATE_OFF; +import static android.view.Display.STATE_ON; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Instrumentation; +import android.content.res.Configuration; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.hardware.display.DisplayManager; +import android.hardware.input.InputSensorInfo; +import android.os.Handler; +import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; +import android.view.Display; +import android.view.Surface; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.devicestate.DeviceStateProvider; +import com.android.server.devicestate.DeviceStateProvider.Listener; +import com.android.server.policy.feature.flags.FakeFeatureFlagsImpl; +import com.android.server.policy.feature.flags.Flags; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.internal.util.reflection.FieldSetter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Unit tests for {@link BookStyleDeviceStatePolicy.Provider}. + * <p/> + * Run with <code>atest BookStyleDeviceStatePolicyTest</code>. + */ +@RunWith(AndroidTestingRunner.class) +public final class BookStyleDeviceStatePolicyTest { + + private static final int DEVICE_STATE_CLOSED = 0; + private static final int DEVICE_STATE_HALF_OPENED = 1; + private static final int DEVICE_STATE_OPENED = 2; + + @Captor + private ArgumentCaptor<Integer> mDeviceStateCaptor; + @Captor + private ArgumentCaptor<DisplayManager.DisplayListener> mDisplayListenerCaptor; + @Mock + private SensorManager mSensorManager; + @Mock + private InputSensorInfo mInputSensorInfo; + @Mock + private Listener mListener; + @Mock + DisplayManager mDisplayManager; + @Mock + private Display mDisplay; + + private final FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl(); + + private final Configuration mConfiguration = new Configuration(); + + private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); + + @Rule + public final TestableContext mContext = new TestableContext( + mInstrumentation.getTargetContext()); + + private Sensor mHallSensor; + private Sensor mOrientationSensor; + private Sensor mHingeAngleSensor; + private Sensor mLeftAccelerometer; + private Sensor mRightAccelerometer; + + private Map<Sensor, List<SensorEventListener>> mSensorEventListeners = new HashMap<>(); + private DeviceStateProvider mProvider; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_FOLDABLES_POSTURE_BASED_CLOSED_STATE, true); + mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_DUAL_DISPLAY_BLOCKING, true); + + when(mInputSensorInfo.getName()).thenReturn("hall-effect"); + mHallSensor = new Sensor(mInputSensorInfo); + when(mInputSensorInfo.getName()).thenReturn("hinge-angle"); + mHingeAngleSensor = new Sensor(mInputSensorInfo); + when(mInputSensorInfo.getName()).thenReturn("left-accelerometer"); + mLeftAccelerometer = new Sensor(mInputSensorInfo); + when(mInputSensorInfo.getName()).thenReturn("right-accelerometer"); + mRightAccelerometer = new Sensor(mInputSensorInfo); + when(mInputSensorInfo.getName()).thenReturn("orientation"); + mOrientationSensor = new Sensor(mInputSensorInfo); + + mContext.addMockSystemService(SensorManager.class, mSensorManager); + + when(mSensorManager.getDefaultSensor(eq(Sensor.TYPE_HINGE_ANGLE), eq(true))) + .thenReturn(mHingeAngleSensor); + when(mSensorManager.getDefaultSensor(eq(Sensor.TYPE_DEVICE_ORIENTATION))) + .thenReturn(mOrientationSensor); + + when(mDisplayManager.getDisplay(eq(DEFAULT_DISPLAY))).thenReturn(mDisplay); + mContext.addMockSystemService(DisplayManager.class, mDisplayManager); + + mContext.ensureTestableResources(); + when(mContext.getResources().getConfiguration()).thenReturn(mConfiguration); + + final List<Sensor> sensors = new ArrayList<>(); + sensors.add(mHallSensor); + sensors.add(mHingeAngleSensor); + sensors.add(mOrientationSensor); + sensors.add(mLeftAccelerometer); + sensors.add(mRightAccelerometer); + + when(mSensorManager.registerListener(any(), any(), anyInt(), any())).thenAnswer( + invocation -> { + final SensorEventListener listener = invocation.getArgument(0); + final Sensor sensor = invocation.getArgument(1); + addSensorListener(sensor, listener); + return true; + }); + when(mSensorManager.registerListener(any(), any(), anyInt())).thenAnswer( + invocation -> { + final SensorEventListener listener = invocation.getArgument(0); + final Sensor sensor = invocation.getArgument(1); + addSensorListener(sensor, listener); + return true; + }); + + doAnswer(invocation -> { + final SensorEventListener listener = invocation.getArgument(0); + final boolean[] removed = {false}; + mSensorEventListeners.forEach((sensor, sensorEventListeners) -> + removed[0] |= sensorEventListeners.remove(listener)); + + if (!removed[0]) { + throw new IllegalArgumentException( + "Trying to unregister listener " + listener + " that was not registered"); + } + + return null; + }).when(mSensorManager).unregisterListener(any(SensorEventListener.class)); + + doAnswer(invocation -> { + final SensorEventListener listener = invocation.getArgument(0); + final Sensor sensor = invocation.getArgument(1); + + boolean removed = mSensorEventListeners.get(sensor).remove(listener); + if (!removed) { + throw new IllegalArgumentException( + "Trying to unregister listener " + listener + + " that was not registered for sensor " + sensor); + } + + return null; + }).when(mSensorManager).unregisterListener(any(SensorEventListener.class), + any(Sensor.class)); + + try { + FieldSetter.setField(mHallSensor, mHallSensor.getClass() + .getDeclaredField("mStringType"), "com.google.sensor.hall_effect"); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + + when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL))) + .thenReturn(sensors); + + mInstrumentation.runOnMainSync(() -> mProvider = createProvider()); + + verify(mDisplayManager, atLeastOnce()).registerDisplayListener( + mDisplayListenerCaptor.capture(), nullable(Handler.class)); + setScreenOn(true); + } + + @Test + public void test_noSensorEventsYet_reportOpenedState() { + mProvider.setListener(mListener); + verify(mListener).onStateChanged(mDeviceStateCaptor.capture()); + assertEquals(DEVICE_STATE_OPENED, mDeviceStateCaptor.getValue().intValue()); + } + + @Test + public void test_deviceClosedSensorEventsBecameAvailable_reportsClosedState() { + mProvider.setListener(mListener); + clearInvocations(mListener); + + sendHingeAngle(0f); + + verify(mListener).onStateChanged(mDeviceStateCaptor.capture()); + assertEquals(DEVICE_STATE_CLOSED, mDeviceStateCaptor.getValue().intValue()); + } + + @Test + public void test_hingeAngleClosed_reportsClosedState() { + sendHingeAngle(0f); + + mProvider.setListener(mListener); + verify(mListener).onStateChanged(mDeviceStateCaptor.capture()); + assertEquals(DEVICE_STATE_CLOSED, mDeviceStateCaptor.getValue().intValue()); + } + + @Test + public void test_hingeAngleFullyOpened_reportsOpenedState() { + sendHingeAngle(180f); + + mProvider.setListener(mListener); + verify(mListener).onStateChanged(mDeviceStateCaptor.capture()); + assertEquals(DEVICE_STATE_OPENED, mDeviceStateCaptor.getValue().intValue()); + } + + @Test + public void test_unfoldingFromClosedToFullyOpened_reportsOpenedEvent() { + sendHingeAngle(0f); + mProvider.setListener(mListener); + clearInvocations(mListener); + + sendHingeAngle(180f); + + verify(mListener).onStateChanged(mDeviceStateCaptor.capture()); + assertEquals(DEVICE_STATE_OPENED, mDeviceStateCaptor.getValue().intValue()); + } + + @Test + public void test_foldingFromFullyOpenToFullyClosed_movesToClosedState() { + sendHingeAngle(180f); + + sendHingeAngle(0f); + + mProvider.setListener(mListener); + verify(mListener).onStateChanged(mDeviceStateCaptor.capture()); + assertEquals(DEVICE_STATE_CLOSED, mDeviceStateCaptor.getValue().intValue()); + } + + @Test + public void test_slowUnfolding_reportsEventsInOrder() { + sendHingeAngle(0f); + mProvider.setListener(mListener); + + sendHingeAngle(5f); + sendHingeAngle(10f); + sendHingeAngle(60f); + sendHingeAngle(100f); + sendHingeAngle(180f); + + verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture()); + assertThat(mDeviceStateCaptor.getAllValues()).containsExactly( + DEVICE_STATE_CLOSED, + DEVICE_STATE_HALF_OPENED, + DEVICE_STATE_OPENED + ); + } + + @Test + public void test_slowFolding_reportsEventsInOrder() { + sendHingeAngle(180f); + mProvider.setListener(mListener); + + sendHingeAngle(180f); + sendHingeAngle(100f); + sendHingeAngle(60f); + sendHingeAngle(10f); + sendHingeAngle(5f); + + verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture()); + assertThat(mDeviceStateCaptor.getAllValues()).containsExactly( + DEVICE_STATE_OPENED, + DEVICE_STATE_HALF_OPENED, + DEVICE_STATE_CLOSED + ); + } + + @Test + public void test_hingeAngleOpen_screenOff_reportsHalfFolded() { + sendHingeAngle(0f); + setScreenOn(false); + mProvider.setListener(mListener); + + sendHingeAngle(10f); + + verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture()); + assertThat(mDeviceStateCaptor.getAllValues()).containsExactly( + DEVICE_STATE_CLOSED, + DEVICE_STATE_HALF_OPENED + ); + } + + @Test + public void test_slowUnfoldingWithScreenOff_reportsEventsInOrder() { + sendHingeAngle(0f); + setScreenOn(false); + mProvider.setListener(mListener); + + sendHingeAngle(5f); + assertLatestReportedState(DEVICE_STATE_HALF_OPENED); + sendHingeAngle(10f); + assertLatestReportedState(DEVICE_STATE_HALF_OPENED); + sendHingeAngle(60f); + assertLatestReportedState(DEVICE_STATE_HALF_OPENED); + sendHingeAngle(100f); + sendHingeAngle(180f); + assertLatestReportedState(DEVICE_STATE_OPENED); + + verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture()); + assertThat(mDeviceStateCaptor.getAllValues()).containsExactly( + DEVICE_STATE_CLOSED, + DEVICE_STATE_HALF_OPENED, + DEVICE_STATE_OPENED + ); + } + + @Test + public void test_unfoldWithScreenOff_reportsHalfOpened() { + sendHingeAngle(0f); + setScreenOn(false); + mProvider.setListener(mListener); + + sendHingeAngle(5f); + sendHingeAngle(10f); + + verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture()); + assertThat(mDeviceStateCaptor.getAllValues()).containsExactly( + DEVICE_STATE_CLOSED, + DEVICE_STATE_HALF_OPENED + ); + } + + @Test + public void test_slowUnfoldingAndFolding_reportsEventsInOrder() { + sendHingeAngle(0f); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_CLOSED); + + // Started unfolding + sendHingeAngle(5f); + sendHingeAngle(30f); + assertLatestReportedState(DEVICE_STATE_HALF_OPENED); + sendHingeAngle(60f); + assertLatestReportedState(DEVICE_STATE_HALF_OPENED); + sendHingeAngle(100f); + assertLatestReportedState(DEVICE_STATE_HALF_OPENED); + sendHingeAngle(180f); + assertLatestReportedState(DEVICE_STATE_OPENED); + + // Started folding + sendHingeAngle(100f); + assertLatestReportedState(DEVICE_STATE_HALF_OPENED); + sendHingeAngle(60f); + assertLatestReportedState(DEVICE_STATE_HALF_OPENED); + sendHingeAngle(30f); + assertLatestReportedState(DEVICE_STATE_CLOSED); + sendHingeAngle(5f); + assertLatestReportedState(DEVICE_STATE_CLOSED); + + verify(mListener, atLeastOnce()).onStateChanged(mDeviceStateCaptor.capture()); + assertThat(mDeviceStateCaptor.getAllValues()).containsExactly( + DEVICE_STATE_CLOSED, + DEVICE_STATE_HALF_OPENED, + DEVICE_STATE_OPENED, + DEVICE_STATE_HALF_OPENED, + DEVICE_STATE_CLOSED + ); + } + + @Test + public void test_unfoldTo30Degrees_screenOnRightSideMostlyFlat_keepsClosedState() { + sendHingeAngle(0f); + sendRightSideFlatSensorEvent(true); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_CLOSED); + clearInvocations(mListener); + + sendHingeAngle(30f); + + verify(mListener, never()).onStateChanged(mDeviceStateCaptor.capture()); + } + + @Test + public void test_unfoldTo30Degrees_seascapeDeviceOrientation_keepsClosedState() { + sendHingeAngle(0f); + sendRightSideFlatSensorEvent(false); + sendDeviceOrientation(Surface.ROTATION_270); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_CLOSED); + clearInvocations(mListener); + + sendHingeAngle(30f); + + verify(mListener, never()).onStateChanged(mDeviceStateCaptor.capture()); + } + + @Test + public void test_unfoldTo30Degrees_landscapeScreenRotation_keepsClosedState() { + sendHingeAngle(0f); + sendRightSideFlatSensorEvent(false); + sendScreenRotation(Surface.ROTATION_90); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_CLOSED); + clearInvocations(mListener); + + sendHingeAngle(30f); + + verify(mListener, never()).onStateChanged(mDeviceStateCaptor.capture()); + } + + @Test + public void test_unfoldTo30Degrees_seascapeScreenRotation_keepsClosedState() { + sendHingeAngle(0f); + sendRightSideFlatSensorEvent(false); + sendScreenRotation(Surface.ROTATION_270); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_CLOSED); + clearInvocations(mListener); + + sendHingeAngle(30f); + + verify(mListener, never()).onStateChanged(mDeviceStateCaptor.capture()); + } + + @Test + public void test_unfoldTo30Degrees_screenOnRightSideNotFlat_switchesToHalfOpenState() { + sendHingeAngle(0f); + sendRightSideFlatSensorEvent(false); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_CLOSED); + clearInvocations(mListener); + + sendHingeAngle(30f); + + verify(mListener).onStateChanged(DEVICE_STATE_HALF_OPENED); + } + + @Test + public void test_unfoldTo30Degrees_screenOffRightSideFlat_switchesToHalfOpenState() { + sendHingeAngle(0f); + setScreenOn(false); + // This sensor event should be ignored as screen is off + sendRightSideFlatSensorEvent(true); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_CLOSED); + clearInvocations(mListener); + + sendHingeAngle(30f); + + verify(mListener).onStateChanged(DEVICE_STATE_HALF_OPENED); + } + + @Test + public void test_unfoldTo60Degrees_andFoldTo10_switchesToClosedState() { + sendHingeAngle(0f); + sendRightSideFlatSensorEvent(false); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_CLOSED); + sendHingeAngle(60f); + assertLatestReportedState(DEVICE_STATE_HALF_OPENED); + clearInvocations(mListener); + + sendHingeAngle(10f); + + verify(mListener).onStateChanged(DEVICE_STATE_CLOSED); + } + + @Test + public void test_foldTo10AndUnfoldTo85Degrees_keepsClosedState() { + sendHingeAngle(0f); + sendRightSideFlatSensorEvent(false); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_CLOSED); + sendHingeAngle(180f); + assertLatestReportedState(DEVICE_STATE_OPENED); + sendHingeAngle(10f); + assertLatestReportedState(DEVICE_STATE_CLOSED); + + sendHingeAngle(85f); + + // Keeps 'tent'/'wedge' mode even when right side is not flat + // as user manually folded the device not all the way + assertLatestReportedState(DEVICE_STATE_CLOSED); + } + + @Test + public void test_foldTo0AndUnfoldTo85Degrees_doesNotKeepClosedState() { + sendHingeAngle(0f); + sendRightSideFlatSensorEvent(false); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_CLOSED); + sendHingeAngle(180f); + assertLatestReportedState(DEVICE_STATE_OPENED); + sendHingeAngle(0f); + assertLatestReportedState(DEVICE_STATE_CLOSED); + + sendHingeAngle(85f); + + // Do not enter 'tent'/'wedge' mode when right side is not flat + // as user fully folded the device before that + assertLatestReportedState(DEVICE_STATE_HALF_OPENED); + } + + @Test + public void test_foldTo10_leftSideIsFlat_keepsInnerScreenForReverseWedge() { + sendHingeAngle(180f); + sendLeftSideFlatSensorEvent(true); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_OPENED); + + sendHingeAngle(10f); + + // Keep the inner screen for reverse wedge mode (e.g. for astrophotography use case) + assertLatestReportedState(DEVICE_STATE_HALF_OPENED); + } + + @Test + public void test_foldTo10_leftSideIsNotFlat_switchesToOuterScreen() { + sendHingeAngle(180f); + sendLeftSideFlatSensorEvent(false); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_OPENED); + + sendHingeAngle(10f); + + // Do not keep the inner screen as it is not reverse wedge mode + assertLatestReportedState(DEVICE_STATE_CLOSED); + } + + @Test + public void test_foldTo10_noAccelerometerEvents_switchesToOuterScreen() { + sendHingeAngle(180f); + mProvider.setListener(mListener); + assertLatestReportedState(DEVICE_STATE_OPENED); + + sendHingeAngle(10f); + + // Do not keep the inner screen as it is not reverse wedge mode + assertLatestReportedState(DEVICE_STATE_CLOSED); + } + + @Test + public void test_deviceClosed_screenIsOff_noSensorListeners() { + mProvider.setListener(mListener); + + sendHingeAngle(0f); + setScreenOn(false); + + assertNoListenersForSensor(mLeftAccelerometer); + assertNoListenersForSensor(mRightAccelerometer); + assertNoListenersForSensor(mOrientationSensor); + } + + @Test + public void test_deviceClosed_screenIsOn_doesNotListenForOneAccelerometer() { + mProvider.setListener(mListener); + + sendHingeAngle(0f); + setScreenOn(true); + + assertNoListenersForSensor(mLeftAccelerometer); + assertListensForSensor(mRightAccelerometer); + assertListensForSensor(mOrientationSensor); + } + + @Test + public void test_deviceOpened_screenIsOn_listensToSensors() { + mProvider.setListener(mListener); + + sendHingeAngle(180f); + setScreenOn(true); + + assertListensForSensor(mLeftAccelerometer); + assertListensForSensor(mRightAccelerometer); + assertListensForSensor(mOrientationSensor); + } + + private void assertLatestReportedState(int state) { + final ArgumentCaptor<Integer> integerCaptor = ArgumentCaptor.forClass(Integer.class); + verify(mListener, atLeastOnce()).onStateChanged(integerCaptor.capture()); + assertEquals(state, integerCaptor.getValue().intValue()); + } + + private void sendHingeAngle(float angle) { + sendSensorEvent(mHingeAngleSensor, new float[]{angle}); + } + + private void sendDeviceOrientation(int orientation) { + sendSensorEvent(mOrientationSensor, new float[]{orientation}); + } + + private void sendScreenRotation(int rotation) { + when(mDisplay.getRotation()).thenReturn(rotation); + mDisplayListenerCaptor.getAllValues().forEach((l) -> l.onDisplayChanged(DEFAULT_DISPLAY)); + } + + private void sendRightSideFlatSensorEvent(boolean flat) { + sendAccelerometerFlatEvents(mRightAccelerometer, flat); + } + + private void sendLeftSideFlatSensorEvent(boolean flat) { + sendAccelerometerFlatEvents(mLeftAccelerometer, flat); + } + + private static final int ACCELEROMETER_EVENTS = 10; + + private void sendAccelerometerFlatEvents(Sensor sensor, boolean flat) { + final float[] values = flat ? new float[]{0.00021f, -0.00013f, 9.7899f} : + new float[]{6.124f, 4.411f, -1.7899f}; + // Send the same values multiple times to bypass noise filter + for (int i = 0; i < ACCELEROMETER_EVENTS; i++) { + sendSensorEvent(sensor, values); + } + } + + private void setScreenOn(boolean isOn) { + int state = isOn ? STATE_ON : STATE_OFF; + when(mDisplay.getState()).thenReturn(state); + mDisplayListenerCaptor.getAllValues().forEach((l) -> l.onDisplayChanged(DEFAULT_DISPLAY)); + } + + private void sendSensorEvent(Sensor sensor, float[] values) { + SensorEvent event = mock(SensorEvent.class); + event.sensor = sensor; + try { + FieldSetter.setField(event, event.getClass().getField("values"), + values); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + + List<SensorEventListener> listeners = mSensorEventListeners.get(sensor); + if (listeners != null) { + listeners.forEach(sensorEventListener -> sensorEventListener.onSensorChanged(event)); + } + } + + private void assertNoListenersForSensor(Sensor sensor) { + final List<SensorEventListener> listeners = mSensorEventListeners.getOrDefault(sensor, + new ArrayList<>()); + assertWithMessage("Expected no listeners for sensor " + sensor + " but found some").that( + listeners).isEmpty(); + } + + private void assertListensForSensor(Sensor sensor) { + final List<SensorEventListener> listeners = mSensorEventListeners.getOrDefault(sensor, + new ArrayList<>()); + assertWithMessage( + "Expected at least one listener for sensor " + sensor).that( + listeners).isNotEmpty(); + } + + private void addSensorListener(Sensor sensor, SensorEventListener listener) { + List<SensorEventListener> listeners = mSensorEventListeners.computeIfAbsent( + sensor, k -> new ArrayList<>()); + listeners.add(listener); + } + + private DeviceStateProvider createProvider() { + return new BookStyleDeviceStatePolicy(mFakeFeatureFlags, mContext, mHingeAngleSensor, + mHallSensor, mLeftAccelerometer, mRightAccelerometer, + /* closeAngleDegrees= */ null).getDeviceStateProvider(); + } +} diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStylePreferredScreenCalculatorTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStylePreferredScreenCalculatorTest.java new file mode 100644 index 000000000000..ae05b3f5c121 --- /dev/null +++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStylePreferredScreenCalculatorTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 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.policy; + + +import static com.android.server.policy.BookStyleStateTransitions.DEFAULT_STATE_TRANSITIONS; + +import static com.google.common.truth.Truth.assertWithMessage; + +import android.testing.AndroidTestingRunner; + +import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen; +import com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle; + +import com.google.common.collect.Lists; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.List; + +/** + * Unit tests for {@link BookStylePreferredScreenCalculator}. + * <p/> + * Run with <code>atest BookStyleClosedStateCalculatorTest</code>. + */ +@RunWith(AndroidTestingRunner.class) +public final class BookStylePreferredScreenCalculatorTest { + + private final BookStylePreferredScreenCalculator mCalculator = + new BookStylePreferredScreenCalculator(DEFAULT_STATE_TRANSITIONS); + + private final List<HingeAngle> mHingeAngleValues = Arrays.asList(HingeAngle.values()); + private final List<Boolean> mLikelyTentModeValues = Arrays.asList(true, false); + private final List<Boolean> mLikelyReverseWedgeModeValues = Arrays.asList(true, false); + + @Test + public void transitionAllStates_noCrashes() { + final List<List<Object>> arguments = Lists.cartesianProduct(Arrays.asList( + mHingeAngleValues, + mLikelyTentModeValues, + mLikelyReverseWedgeModeValues + )); + + arguments.forEach(objects -> { + final HingeAngle hingeAngle = (HingeAngle) objects.get(0); + final boolean likelyTent = (boolean) objects.get(1); + final boolean likelyReverseWedge = (boolean) objects.get(2); + + final String description = + "Input: hinge angle = " + hingeAngle + ", likelyTent = " + likelyTent + + ", likelyReverseWedge = " + likelyReverseWedge; + + // Verify that there are no crashes because of infinite state transitions and + // that it returns a valid active state + try { + PreferredScreen preferredScreen = mCalculator.calculatePreferredScreen(hingeAngle, likelyTent, + likelyReverseWedge); + + assertWithMessage(description).that(preferredScreen).isNotEqualTo(PreferredScreen.INVALID); + } catch (Throwable exception) { + throw new AssertionError(description, exception); + } + }); + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index c67e7c5ae61e..b29fc8828f58 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -824,6 +824,16 @@ public final class DisplayDeviceConfigTest { mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels( AUTO_BRIGHTNESS_MODE_DOZE, Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), SMALL_DELTA); + + // Should fall back to the normal preset + assertArrayEquals(new float[]{0.0f, 95}, + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux( + AUTO_BRIGHTNESS_MODE_DOZE, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), ZERO_DELTA); + assertArrayEquals(new float[]{0.35f, 0.45f}, + mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels( + AUTO_BRIGHTNESS_MODE_DOZE, + Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), SMALL_DELTA); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java index e2c338ac8767..7e1dc08f301e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java @@ -202,7 +202,7 @@ public class ActiveServicesTest { final ServiceInfo regularService = new ServiceInfo(); regularService.processName = "com.foo"; String processName = ActiveServices.getProcessNameForService(regularService, null, null, - null, false, false); + null, false, false, false); assertEquals("com.foo", processName); // Isolated service @@ -211,29 +211,90 @@ public class ActiveServicesTest { isolatedService.flags = ServiceInfo.FLAG_ISOLATED_PROCESS; final ComponentName component = new ComponentName("com.foo", "barService"); processName = ActiveServices.getProcessNameForService(isolatedService, component, - null, null, false, false); + null, null, false, false, false); assertEquals("com.foo:barService", processName); + // Isolated Service in package private process. + final ServiceInfo isolatedService1 = new ServiceInfo(); + isolatedService1.processName = "com.foo:trusted_isolated"; + isolatedService1.flags = ServiceInfo.FLAG_ISOLATED_PROCESS; + final ComponentName componentName = new ComponentName("com.foo", "barService"); + processName = ActiveServices.getProcessNameForService(isolatedService1, componentName, + null, null, false, false, false); + assertEquals("com.foo:trusted_isolated:barService", processName); + + // Isolated service in package-private shared process (main process) + final ServiceInfo isolatedPackageSharedService = new ServiceInfo(); + final ComponentName componentName1 = new ComponentName("com.foo", "barService"); + isolatedPackageSharedService.processName = "com.foo"; + isolatedPackageSharedService.applicationInfo = new ApplicationInfo(); + isolatedPackageSharedService.applicationInfo.processName = "com.foo"; + isolatedPackageSharedService.flags = ServiceInfo.FLAG_ISOLATED_PROCESS; + String packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService( + isolatedPackageSharedService, componentName1, null, null, false, false, true); + assertEquals("com.foo:barService", packageSharedIsolatedProcessName); + + // Isolated service in package-private shared process + final ServiceInfo isolatedPackageSharedService1 = new ServiceInfo( + isolatedPackageSharedService); + isolatedPackageSharedService1.processName = "com.foo:trusted_isolated"; + isolatedPackageSharedService1.applicationInfo = new ApplicationInfo(); + isolatedPackageSharedService1.applicationInfo.processName = "com.foo"; + isolatedPackageSharedService1.flags = ServiceInfo.FLAG_ISOLATED_PROCESS; + packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService( + isolatedPackageSharedService1, componentName1, null, null, false, false, true); + assertEquals("com.foo:trusted_isolated", packageSharedIsolatedProcessName); + + + // Bind another one in the same isolated process + final ServiceInfo isolatedPackageSharedService2 = new ServiceInfo( + isolatedPackageSharedService1); + packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService( + isolatedPackageSharedService2, componentName1, null, null, false, false, true); + assertEquals("com.foo:trusted_isolated", packageSharedIsolatedProcessName); + + // Simulate another app trying to do the bind. + final ServiceInfo isolatedPackageSharedService3 = new ServiceInfo( + isolatedPackageSharedService1); + final String auxCallingPackage = "com.bar"; + packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService( + isolatedPackageSharedService3, componentName1, auxCallingPackage, null, + false, false, true); + assertEquals("com.foo:trusted_isolated", packageSharedIsolatedProcessName); + + // Simulate another app owning the service + final ServiceInfo isolatedOtherPackageSharedService = new ServiceInfo( + isolatedPackageSharedService1); + final ComponentName componentName2 = new ComponentName("com.bar", "barService"); + isolatedOtherPackageSharedService.processName = "com.bar:isolated"; + isolatedPackageSharedService.applicationInfo.processName = "com.bar"; + final String mainCallingPackage = "com.foo"; + packageSharedIsolatedProcessName = ActiveServices.getProcessNameForService( + isolatedOtherPackageSharedService, componentName2, mainCallingPackage, + null, false, false, true); + assertEquals("com.bar:isolated", packageSharedIsolatedProcessName); + // Isolated service in shared isolated process final ServiceInfo isolatedServiceShared1 = new ServiceInfo(); isolatedServiceShared1.flags = ServiceInfo.FLAG_ISOLATED_PROCESS; final String instanceName = "pool"; final String callingPackage = "com.foo"; final String sharedIsolatedProcessName1 = ActiveServices.getProcessNameForService( - isolatedServiceShared1, null, callingPackage, instanceName, false, true); + isolatedServiceShared1, null, callingPackage, instanceName, false, true, false); assertEquals("com.foo:ishared:pool", sharedIsolatedProcessName1); // Bind another one in the same isolated process final ServiceInfo isolatedServiceShared2 = new ServiceInfo(isolatedServiceShared1); final String sharedIsolatedProcessName2 = ActiveServices.getProcessNameForService( - isolatedServiceShared2, null, callingPackage, instanceName, false, true); + isolatedServiceShared2, null, callingPackage, instanceName, false, true, false); assertEquals(sharedIsolatedProcessName1, sharedIsolatedProcessName2); // Simulate another app trying to do the bind final ServiceInfo isolatedServiceShared3 = new ServiceInfo(isolatedServiceShared1); final String otherCallingPackage = "com.bar"; final String sharedIsolatedProcessName3 = ActiveServices.getProcessNameForService( - isolatedServiceShared3, null, otherCallingPackage, instanceName, false, true); + isolatedServiceShared3, null, otherCallingPackage, instanceName, false, true, + false); Assert.assertNotEquals(sharedIsolatedProcessName2, sharedIsolatedProcessName3); } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index 650c473533ed..116d5db45023 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -26,15 +26,18 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; +import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX; +import static com.android.server.job.controllers.FlexibilityController.FLEXIBLE_CONSTRAINTS; import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS; import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_APPLIED_CONSTRAINTS; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS; -import static com.android.server.job.controllers.FlexibilityController.FLEXIBLE_CONSTRAINTS; import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING; @@ -50,24 +53,32 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.AlarmManager; import android.app.AppGlobals; import android.app.job.JobInfo; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.net.NetworkRequest; import android.os.Looper; +import android.os.PowerManager; import android.provider.DeviceConfig; import android.util.ArraySet; +import android.util.EmptyArray; import com.android.server.AppSchedulingModuleThread; +import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; import com.android.server.job.JobStore; @@ -77,6 +88,7 @@ import libcore.junit.util.compat.CoreCompatChangeRule; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoSession; @@ -95,6 +107,7 @@ public class FlexibilityControllerTest { private static final long FROZEN_TIME = 100L; private MockitoSession mMockingSession; + private BroadcastReceiver mBroadcastReceiver; private FlexibilityController mFlexibilityController; private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder; private JobStore mJobStore; @@ -106,6 +119,8 @@ public class FlexibilityControllerTest { @Mock private Context mContext; @Mock + private DeviceIdleInternal mDeviceIdleInternal; + @Mock private JobSchedulerService mJobSchedulerService; @Mock private PrefetchController mPrefetchController; @@ -128,10 +143,13 @@ public class FlexibilityControllerTest { // Called in FlexibilityController constructor. when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager); + doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(), any(), any()); when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.hasSystemFeature( PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false); when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_EMBEDDED)).thenReturn(false); + doReturn(mDeviceIdleInternal) + .when(() -> LocalServices.getService(DeviceIdleInternal.class)); // Used in FlexibilityController.FcConstants. doAnswer((Answer<Void>) invocationOnMock -> null) .when(() -> DeviceConfig.addOnPropertiesChangedListener( @@ -146,7 +164,7 @@ public class FlexibilityControllerTest { eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any())); //used to get jobs by UID mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir()); - when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore); + doReturn(mJobStore).when(mJobSchedulerService).getJobStore(); // Used in JobStatus. doReturn(mock(PackageManagerInternal.class)) .when(() -> LocalServices.getService(PackageManagerInternal.class)); @@ -156,6 +174,8 @@ public class FlexibilityControllerTest { JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC); // Initialize real objects. + ArgumentCaptor<BroadcastReceiver> receiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); mFlexibilityController = new FlexibilityController(mJobSchedulerService, mPrefetchController); mFcConfig = mFlexibilityController.getFcConfig(); @@ -166,6 +186,11 @@ public class FlexibilityControllerTest { setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L); setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS); waitForQuietModuleThread(); + + verify(mContext).registerReceiver(receiverCaptor.capture(), + ArgumentMatchers.argThat(filter -> + filter.hasAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED))); + mBroadcastReceiver = receiverCaptor.getValue(); } @After @@ -212,6 +237,7 @@ public class FlexibilityControllerTest { JobStatus js = JobStatus.createFromJobInfo( jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag); js.enqueueTime = FROZEN_TIME; + js.setStandbyBucket(ACTIVE_INDEX); if (js.hasFlexibilityConstraint()) { js.setNumAppliedFlexibleConstraints(Integer.bitCount( mFlexibilityController.getRelevantAppliedConstraintsLocked(js))); @@ -598,10 +624,10 @@ public class FlexibilityControllerTest { @Test public void testGetLifeCycleBeginningElapsedLocked_Prefetch() { // prefetch with lifecycle - when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(700L); + doReturn(700L).when(mPrefetchController).getLaunchTimeThresholdMs(); JobInfo.Builder jb = createJob(0).setPrefetch(true); JobStatus js = createJobStatus("time", jb); - when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(900L); + doReturn(900L).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js); assertEquals(900L - 700L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js)); // prefetch with enqueue jb = createJob(0).setPrefetch(true); @@ -616,7 +642,7 @@ public class FlexibilityControllerTest { // prefetch without estimate mFlexibilityController.mPrefetchLifeCycleStart .add(js.getUserId(), js.getSourcePackageName(), 500L); - when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(Long.MAX_VALUE); + doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js); jb = createJob(0).setPrefetch(true); js = createJobStatus("time", jb); assertEquals(500L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js)); @@ -642,12 +668,12 @@ public class FlexibilityControllerTest { // prefetch no estimate JobInfo.Builder jb = createJob(0).setPrefetch(true); JobStatus js = createJobStatus("time", jb); - when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(Long.MAX_VALUE); + doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js); assertEquals(Long.MAX_VALUE, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); // prefetch with estimate jb = createJob(0).setPrefetch(true); js = createJobStatus("time", jb); - when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(1000L); + doReturn(1000L).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js); assertEquals(1000L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); } @@ -696,7 +722,7 @@ public class FlexibilityControllerTest { // Stop satisfied constraints from causing a false positive. js.setNumAppliedFlexibleConstraints(100); synchronized (mFlexibilityController.mLock) { - when(mJobSchedulerService.isCurrentlyRunningLocked(js)).thenReturn(true); + doReturn(true).when(mJobSchedulerService).isCurrentlyRunningLocked(js); assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js)); } } @@ -847,14 +873,85 @@ public class FlexibilityControllerTest { } @Test + public void testAllowlistedAppBypass() { + setPowerWhitelistExceptIdle(); + mFlexibilityController.onSystemServicesReady(); + + JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_HIGH)); + JobStatus jsDefault = createJobStatus("testAllowlistedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)); + JobStatus jsLow = createJobStatus("testAllowlistedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_LOW)); + JobStatus jsMin = createJobStatus("testAllowlistedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_MIN)); + jsHigh.setStandbyBucket(EXEMPTED_INDEX); + jsDefault.setStandbyBucket(EXEMPTED_INDEX); + jsLow.setStandbyBucket(EXEMPTED_INDEX); + jsMin.setStandbyBucket(EXEMPTED_INDEX); + + setPowerWhitelistExceptIdle(); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin)); + } + + setPowerWhitelistExceptIdle(SOURCE_PACKAGE); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin)); + } + } + + @Test + public void testForegroundAppBypass() { + JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_HIGH)); + JobStatus jsDefault = createJobStatus("testAllowlistedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)); + JobStatus jsLow = createJobStatus("testAllowlistedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_LOW)); + JobStatus jsMin = createJobStatus("testAllowlistedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_MIN)); + + doReturn(JobInfo.BIAS_DEFAULT).when(mJobSchedulerService).getUidBias(mSourceUid); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin)); + } + + setUidBias(mSourceUid, JobInfo.BIAS_BOUND_FOREGROUND_SERVICE); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin)); + } + + setUidBias(mSourceUid, JobInfo.BIAS_FOREGROUND_SERVICE); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin)); + } + } + + @Test public void testTopAppBypass() { - JobInfo.Builder jb = createJob(0); + JobInfo.Builder jb = createJob(0).setPriority(JobInfo.PRIORITY_MIN); JobStatus js = createJobStatus("testTopAppBypass", jb); mJobStore.add(js); // Needed because if before and after Uid bias is the same, nothing happens. when(mJobSchedulerService.getUidBias(mSourceUid)) - .thenReturn(JobInfo.BIAS_FOREGROUND_SERVICE); + .thenReturn(JobInfo.BIAS_DEFAULT); synchronized (mFlexibilityController.mLock) { mFlexibilityController.maybeStartTrackingJobLocked(js, null); @@ -865,7 +962,7 @@ public class FlexibilityControllerTest { assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js)); assertTrue(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)); - setUidBias(mSourceUid, JobInfo.BIAS_FOREGROUND_SERVICE); + setUidBias(mSourceUid, JobInfo.BIAS_SYNC_INITIALIZATION); assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js)); assertFalse(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)); @@ -1187,9 +1284,9 @@ public class FlexibilityControllerTest { JobInfo.Builder jb = createJob(22).setPrefetch(true); JobStatus js = createJobStatus("onPrefetchCacheUpdated", jb); jobs.add(js); - when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(7 * HOUR_IN_MILLIS); - when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn( - 1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS); + doReturn(7 * HOUR_IN_MILLIS).when(mPrefetchController).getLaunchTimeThresholdMs(); + doReturn(1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS) + .when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js); mFlexibilityController.maybeStartTrackingJobLocked(js, null); @@ -1245,7 +1342,6 @@ public class FlexibilityControllerTest { setUidBias(mSourceUid, BIAS_FOREGROUND_SERVICE); assertEquals(100L, (long) mFlexibilityController .mPrefetchLifeCycleStart.get(js.getSourceUserId(), js.getSourcePackageName())); - } @Test @@ -1259,7 +1355,7 @@ public class FlexibilityControllerTest { } private void runTestUnsupportedDevice(String feature) { - when(mPackageManager.hasSystemFeature(feature)).thenReturn(true); + doReturn(true).when(mPackageManager).hasSystemFeature(feature); mFlexibilityController = new FlexibilityController(mJobSchedulerService, mPrefetchController); assertFalse(mFlexibilityController.isEnabled()); @@ -1279,6 +1375,16 @@ public class FlexibilityControllerTest { } } + private void setPowerWhitelistExceptIdle(String... packages) { + doReturn(packages == null ? EmptyArray.STRING : packages) + .when(mDeviceIdleInternal).getFullPowerWhitelistExceptIdle(); + if (mBroadcastReceiver != null) { + mBroadcastReceiver.onReceive(mContext, + new Intent(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED)); + waitForQuietModuleThread(); + } + } + private void setUidBias(int uid, int bias) { int prevBias = mJobSchedulerService.getUidBias(uid); doReturn(bias).when(mJobSchedulerService).getUidBias(uid); diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index e1f490ae3e2f..5e7deef9f424 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -608,6 +608,20 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void testIsInputDeviceOwnedByVirtualDevice() { + assertThat(mLocalService.isInputDeviceOwnedByVirtualDevice(INPUT_DEVICE_ID)).isFalse(); + + final int fd = 1; + mInputController.addDeviceForTesting(BINDER, fd, + InputController.InputDeviceDescriptor.TYPE_KEYBOARD, DISPLAY_ID_1, PHYS, + DEVICE_NAME_1, INPUT_DEVICE_ID); + assertThat(mLocalService.isInputDeviceOwnedByVirtualDevice(INPUT_DEVICE_ID)).isTrue(); + + mInputController.unregisterInputDevice(BINDER); + assertThat(mLocalService.isInputDeviceOwnedByVirtualDevice(INPUT_DEVICE_ID)).isFalse(); + } + + @Test public void getDeviceIdsForUid_noRunningApps_returnsNull() { assertThat(mLocalService.getDeviceIdsForUid(UID_1)).isEmpty(); assertThat(mVdmNative.getDeviceIdsForUid(UID_1)).isEmpty(); diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java index 3530e38ef67c..ae0a758449b5 100644 --- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java +++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java @@ -85,7 +85,7 @@ public class TestSystemImpl implements SystemInterface { private void enablePackageForUser(String packageName, boolean enable, int userId) { Map<Integer, PackageInfo> userPackages = mPackages.get(packageName); if (userPackages == null) { - throw new IllegalArgumentException("There is no package called " + packageName); + return; } PackageInfo packageInfo = userPackages.get(userId); packageInfo.applicationInfo.enabled = enable; diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java index 32082e3d857e..5a06327fdde3 100644 --- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java @@ -127,12 +127,21 @@ public class WebViewUpdateServiceTest { private void checkCertainPackageUsedAfterWebViewBootPreparation(String expectedProviderName, WebViewProviderInfo[] webviewPackages) { checkCertainPackageUsedAfterWebViewBootPreparation( - expectedProviderName, webviewPackages, 1); + expectedProviderName, webviewPackages, 1, null); } private void checkCertainPackageUsedAfterWebViewBootPreparation(String expectedProviderName, - WebViewProviderInfo[] webviewPackages, int numRelros) { + WebViewProviderInfo[] webviewPackages, String userSetting) { + checkCertainPackageUsedAfterWebViewBootPreparation( + expectedProviderName, webviewPackages, 1, userSetting); + } + + private void checkCertainPackageUsedAfterWebViewBootPreparation(String expectedProviderName, + WebViewProviderInfo[] webviewPackages, int numRelros, String userSetting) { setupWithPackagesAndRelroCount(webviewPackages, numRelros); + if (userSetting != null) { + mTestSystemImpl.updateUserSetting(null, userSetting); + } // Add (enabled and valid) package infos for each provider setEnabledAndValidPackageInfos(webviewPackages); @@ -280,7 +289,7 @@ public class WebViewUpdateServiceTest { singlePackage, new WebViewProviderInfo[] { new WebViewProviderInfo(singlePackage, "", true /*def av*/, false, null)}, - 2); + 2, null); } // Ensure that package with valid signatures is chosen rather than package with invalid @@ -295,14 +304,16 @@ public class WebViewUpdateServiceTest { Signature invalidPackageSignature = new Signature("33"); WebViewProviderInfo[] packages = new WebViewProviderInfo[] { - new WebViewProviderInfo(invalidPackage, "", true, false, new String[]{ - Base64.encodeToString( - invalidExpectedSignature.toByteArray(), Base64.DEFAULT)}), new WebViewProviderInfo(validPackage, "", true, false, new String[]{ Base64.encodeToString( - validSignature.toByteArray(), Base64.DEFAULT)}) + validSignature.toByteArray(), Base64.DEFAULT)}), + new WebViewProviderInfo(invalidPackage, "", true, false, new String[]{ + Base64.encodeToString( + invalidExpectedSignature.toByteArray(), Base64.DEFAULT)}) }; setupWithPackagesNonDebuggable(packages); + // Start with the setting pointing to the invalid package + mTestSystemImpl.updateUserSetting(null, invalidPackage); mTestSystemImpl.setPackageInfo(createPackageInfo(invalidPackage, true /* enabled */, true /* valid */, true /* installed */, new Signature[]{invalidPackageSignature} , 0 /* updateTime */)); @@ -339,7 +350,9 @@ public class WebViewUpdateServiceTest { } @Test - public void testFailListingEmptyWebviewPackages() { + @RequiresFlagsDisabled("android.webkit.update_service_v2") + // If the flag is set, will throw an exception because of no available by default provider. + public void testEmptyConfig() { WebViewProviderInfo[] packages = new WebViewProviderInfo[0]; setupWithPackages(packages); setEnabledAndValidPackageInfos(packages); @@ -352,14 +365,26 @@ public class WebViewUpdateServiceTest { WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage()); + } - // Now install a package + @Test + public void testFailListingEmptyWebviewPackages() { String singlePackage = "singlePackage"; - packages = new WebViewProviderInfo[]{ + WebViewProviderInfo[] packages = new WebViewProviderInfo[]{ new WebViewProviderInfo(singlePackage, "", true, false, null)}; setupWithPackages(packages); - setEnabledAndValidPackageInfos(packages); + runWebViewBootPreparationOnMainSync(); + + Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged( + Matchers.anyObject()); + + WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); + assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage()); + + // Now install the package + setEnabledAndValidPackageInfos(packages); mWebViewUpdateServiceImpl.packageStateChanged(singlePackage, WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID); @@ -370,7 +395,7 @@ public class WebViewUpdateServiceTest { // Remove the package again mTestSystemImpl.removePackageInfo(singlePackage); mWebViewUpdateServiceImpl.packageStateChanged(singlePackage, - WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID); + WebViewUpdateService.PACKAGE_REMOVED, TestSystemImpl.PRIMARY_USER_ID); // Package removed - ensure our interface states that there is no package response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); @@ -455,6 +480,8 @@ public class WebViewUpdateServiceTest { new WebViewProviderInfo(firstPackage, "", true, false, null), new WebViewProviderInfo(secondPackage, "", true, false, null)}; setupWithPackages(packages); + // Start with the setting pointing to the second package + mTestSystemImpl.updateUserSetting(null, secondPackage); // Have all packages be enabled, so that we can change provider however we want to setEnabledAndValidPackageInfos(packages); @@ -463,9 +490,9 @@ public class WebViewUpdateServiceTest { runWebViewBootPreparationOnMainSync(); Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( - Mockito.argThat(new IsPackageInfoWithName(firstPackage))); + Mockito.argThat(new IsPackageInfoWithName(secondPackage))); - assertEquals(firstPackage, + assertEquals(secondPackage, mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName); new Thread(new Runnable() { @@ -474,12 +501,13 @@ public class WebViewUpdateServiceTest { WebViewProviderResponse threadResponse = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_SUCCESS, threadResponse.status); - assertEquals(secondPackage, threadResponse.packageInfo.packageName); - // Verify that we killed the first package if we performed a settings change - - // otherwise we had to disable the first package, in which case its dependents + assertEquals(firstPackage, threadResponse.packageInfo.packageName); + // Verify that we killed the second package if we performed a settings change - + // otherwise we had to disable the second package, in which case its dependents // should have been killed by the framework. if (settingsChange) { - Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(firstPackage)); + Mockito.verify(mTestSystemImpl) + .killPackageDependents(Mockito.eq(secondPackage)); } countdown.countDown(); } @@ -490,32 +518,36 @@ public class WebViewUpdateServiceTest { } if (settingsChange) { - mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage); + mWebViewUpdateServiceImpl.changeProviderAndSetting(firstPackage); } else { - // Enable the second provider - mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, + // Enable the first provider + mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged( - secondPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID); + firstPackage, + WebViewUpdateService.PACKAGE_CHANGED, + TestSystemImpl.PRIMARY_USER_ID); // Ensure we haven't changed package yet. - assertEquals(firstPackage, + assertEquals(secondPackage, mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName); - // Switch provider by disabling the first one - mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, false /* enabled */, + // Switch provider by disabling the second one + mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, false /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged( - firstPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID); + secondPackage, + WebViewUpdateService.PACKAGE_CHANGED, + TestSystemImpl.PRIMARY_USER_ID); } mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); - // first package done, should start on second + // second package done, should start on first Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( - Mockito.argThat(new IsPackageInfoWithName(secondPackage))); + Mockito.argThat(new IsPackageInfoWithName(firstPackage))); mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); - // second package done, the other thread should now be unblocked + // first package done, the other thread should now be unblocked try { countdown.await(); } catch (InterruptedException e) { @@ -526,6 +558,7 @@ public class WebViewUpdateServiceTest { * Scenario for testing re-enabling a fallback package. */ @Test + @RequiresFlagsDisabled("android.webkit.update_service_v2") public void testFallbackPackageEnabling() { String testPackage = "testFallback"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { @@ -555,6 +588,9 @@ public class WebViewUpdateServiceTest { * 3. Primary should be used */ @Test + @RequiresFlagsDisabled("android.webkit.update_service_v2") + // If the flag is set, we don't automitally switch to secondary package unless it is + // chosen directly. public void testInstallingPrimaryPackage() { String primaryPackage = "primary"; String secondaryPackage = "secondary"; @@ -586,16 +622,16 @@ public class WebViewUpdateServiceTest { } @Test - public void testRemovingPrimarySelectsSecondarySingleUser() { + public void testRemovingSecondarySelectsPrimarySingleUser() { for (PackageRemovalType removalType : REMOVAL_TYPES) { - checkRemovingPrimarySelectsSecondary(false /* multiUser */, removalType); + checkRemovingSecondarySelectsPrimary(false /* multiUser */, removalType); } } @Test - public void testRemovingPrimarySelectsSecondaryMultiUser() { + public void testRemovingSecondarySelectsPrimaryMultiUser() { for (PackageRemovalType removalType : REMOVAL_TYPES) { - checkRemovingPrimarySelectsSecondary(true /* multiUser */, removalType); + checkRemovingSecondarySelectsPrimary(true /* multiUser */, removalType); } } @@ -609,7 +645,7 @@ public class WebViewUpdateServiceTest { private PackageRemovalType[] REMOVAL_TYPES = PackageRemovalType.class.getEnumConstants(); - public void checkRemovingPrimarySelectsSecondary(boolean multiUser, + private void checkRemovingSecondarySelectsPrimary(boolean multiUser, PackageRemovalType removalType) { String primaryPackage = "primary"; String secondaryPackage = "secondary"; @@ -620,6 +656,8 @@ public class WebViewUpdateServiceTest { secondaryPackage, "", true /* default available */, false /* fallback */, null)}; setupWithPackages(packages); + // Start with the setting pointing to the secondary package + mTestSystemImpl.updateUserSetting(null, secondaryPackage); int secondaryUserId = 10; int userIdToChangePackageFor = multiUser ? secondaryUserId : TestSystemImpl.PRIMARY_USER_ID; if (multiUser) { @@ -629,31 +667,31 @@ public class WebViewUpdateServiceTest { setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages); runWebViewBootPreparationOnMainSync(); - checkPreparationPhasesForPackage(primaryPackage, 1); + checkPreparationPhasesForPackage(secondaryPackage, 1); boolean enabled = !(removalType == PackageRemovalType.DISABLE); boolean installed = !(removalType == PackageRemovalType.UNINSTALL); boolean hidden = (removalType == PackageRemovalType.HIDE); - // Disable primary package and ensure secondary becomes used + // Disable secondary package and ensure primary becomes used mTestSystemImpl.setPackageInfoForUser(userIdToChangePackageFor, - createPackageInfo(primaryPackage, enabled /* enabled */, true /* valid */, + createPackageInfo(secondaryPackage, enabled /* enabled */, true /* valid */, installed /* installed */, null /* signature */, 0 /* updateTime */, hidden /* hidden */)); - mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, + mWebViewUpdateServiceImpl.packageStateChanged(secondaryPackage, removalType == PackageRemovalType.DISABLE ? WebViewUpdateService.PACKAGE_CHANGED : WebViewUpdateService.PACKAGE_REMOVED, userIdToChangePackageFor); // USER ID - checkPreparationPhasesForPackage(secondaryPackage, 1); + checkPreparationPhasesForPackage(primaryPackage, 1); - // Again enable primary package and verify primary is used + // Again enable secondary package and verify secondary is used mTestSystemImpl.setPackageInfoForUser(userIdToChangePackageFor, - createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, + createPackageInfo(secondaryPackage, true /* enabled */, true /* valid */, true /* installed */)); - mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, + mWebViewUpdateServiceImpl.packageStateChanged(secondaryPackage, removalType == PackageRemovalType.DISABLE ? WebViewUpdateService.PACKAGE_CHANGED : WebViewUpdateService.PACKAGE_ADDED, userIdToChangePackageFor); - checkPreparationPhasesForPackage(primaryPackage, 2); + checkPreparationPhasesForPackage(secondaryPackage, 2); } /** @@ -671,18 +709,20 @@ public class WebViewUpdateServiceTest { secondaryPackage, "", true /* default available */, false /* fallback */, null)}; setupWithPackages(packages); + // Start with the setting pointing to the secondary package + mTestSystemImpl.updateUserSetting(null, secondaryPackage); setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages); int newUser = 100; mTestSystemImpl.addUser(newUser); - // Let the primary package be uninstalled for the new user + // Let the secondary package be uninstalled for the new user mTestSystemImpl.setPackageInfoForUser(newUser, - createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, + createPackageInfo(secondaryPackage, true /* enabled */, true /* valid */, false /* installed */)); mTestSystemImpl.setPackageInfoForUser(newUser, - createPackageInfo(secondaryPackage, true /* enabled */, true /* valid */, + createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.handleNewUser(newUser); - checkPreparationPhasesForPackage(secondaryPackage, 1 /* numRelros */); + checkPreparationPhasesForPackage(primaryPackage, 1 /* numRelros */); } /** @@ -780,9 +820,9 @@ public class WebViewUpdateServiceTest { String chosenPackage = "chosenPackage"; String nonChosenPackage = "non-chosenPackage"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { - new WebViewProviderInfo(chosenPackage, "", true /* default available */, - false /* fallback */, null), new WebViewProviderInfo(nonChosenPackage, "", true /* default available */, + false /* fallback */, null), + new WebViewProviderInfo(chosenPackage, "", true /* default available */, false /* fallback */, null)}; setupWithPackages(packages); @@ -810,6 +850,9 @@ public class WebViewUpdateServiceTest { } @Test + @RequiresFlagsDisabled("android.webkit.update_service_v2") + // If the flag is set, we don't automitally switch to second package unless it is chosen + // directly. public void testRecoverFailedListingWebViewPackagesAddedPackage() { checkRecoverAfterFailListingWebviewPackages(false); } @@ -874,22 +917,22 @@ public class WebViewUpdateServiceTest { false /* fallback */, null), new WebViewProviderInfo(secondPackage, "", true /* default available */, false /* fallback */, null)}; - checkCertainPackageUsedAfterWebViewBootPreparation(firstPackage, packages); + checkCertainPackageUsedAfterWebViewBootPreparation(secondPackage, packages, secondPackage); // Replace or remove the current webview package if (replaced) { mTestSystemImpl.setPackageInfo( - createPackageInfo(firstPackage, true /* enabled */, false /* valid */, + createPackageInfo(secondPackage, true /* enabled */, false /* valid */, true /* installed */)); - mWebViewUpdateServiceImpl.packageStateChanged(firstPackage, + mWebViewUpdateServiceImpl.packageStateChanged(secondPackage, WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID); } else { - mTestSystemImpl.removePackageInfo(firstPackage); - mWebViewUpdateServiceImpl.packageStateChanged(firstPackage, + mTestSystemImpl.removePackageInfo(secondPackage); + mWebViewUpdateServiceImpl.packageStateChanged(secondPackage, WebViewUpdateService.PACKAGE_REMOVED, TestSystemImpl.PRIMARY_USER_ID); } - checkPreparationPhasesForPackage(secondPackage, 1); + checkPreparationPhasesForPackage(firstPackage, 1); Mockito.verify(mTestSystemImpl, Mockito.never()).killPackageDependents( Mockito.anyObject()); @@ -1073,10 +1116,12 @@ public class WebViewUpdateServiceTest { } /** - * Ensure that the update service does use an uninstalled package when that is the only + * Ensure that the update service does not use an uninstalled package even if it is the only * package available. */ @Test + @RequiresFlagsDisabled("android.webkit.update_service_v2") + // If the flag is set, we return the package even if it is not installed. public void testWithSingleUninstalledPackage() { String testPackageName = "test.package.name"; WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] { @@ -1115,12 +1160,14 @@ public class WebViewUpdateServiceTest { String installedPackage = "installedPackage"; String uninstalledPackage = "uninstalledPackage"; WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] { - new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */, - false /* fallback */, null), new WebViewProviderInfo(installedPackage, "", true /* available by default */, + false /* fallback */, null), + new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */, false /* fallback */, null)}; setupWithPackages(webviewPackages); + // Start with the setting pointing to the uninstalled package + mTestSystemImpl.updateUserSetting(null, uninstalledPackage); int secondaryUserId = 5; if (multiUser) { mTestSystemImpl.addUser(secondaryUserId); @@ -1128,7 +1175,7 @@ public class WebViewUpdateServiceTest { setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages); mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo( installedPackage, true /* enabled */, true /* valid */, true /* installed */)); - // Hide or uninstall the primary package for the second user + // Hide or uninstall the secondary package for the second user mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */, true /* valid */, (testUninstalled ? false : true) /* installed */, null /* signatures */, 0 /* updateTime */, (testHidden ? true : false))); @@ -1166,12 +1213,14 @@ public class WebViewUpdateServiceTest { String installedPackage = "installedPackage"; String uninstalledPackage = "uninstalledPackage"; WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] { - new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */, - false /* fallback */, null), new WebViewProviderInfo(installedPackage, "", true /* available by default */, + false /* fallback */, null), + new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */, false /* fallback */, null)}; setupWithPackages(webviewPackages); + // Start with the setting pointing to the uninstalled package + mTestSystemImpl.updateUserSetting(null, uninstalledPackage); int secondaryUserId = 412; mTestSystemImpl.addUser(secondaryUserId); @@ -1221,12 +1270,14 @@ public class WebViewUpdateServiceTest { String installedPackage = "installedPackage"; String uninstalledPackage = "uninstalledPackage"; WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] { - new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */, - false /* fallback */, null), new WebViewProviderInfo(installedPackage, "", true /* available by default */, + false /* fallback */, null), + new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */, false /* fallback */, null)}; setupWithPackages(webviewPackages); + // Start with the setting pointing to the uninstalled package + mTestSystemImpl.updateUserSetting(null, uninstalledPackage); int secondaryUserId = 4; mTestSystemImpl.addUser(secondaryUserId); @@ -1433,11 +1484,16 @@ public class WebViewUpdateServiceTest { new WebViewProviderInfo(newSdkPackage.packageName, "", true, false, null); WebViewProviderInfo currentSdkProviderInfo = new WebViewProviderInfo(currentSdkPackage.packageName, "", true, false, null); - WebViewProviderInfo[] packages = new WebViewProviderInfo[] { - new WebViewProviderInfo(oldSdkPackage.packageName, "", true, false, null), - currentSdkProviderInfo, newSdkProviderInfo}; + WebViewProviderInfo[] packages = + new WebViewProviderInfo[] { + currentSdkProviderInfo, + new WebViewProviderInfo(oldSdkPackage.packageName, "", true, false, null), + newSdkProviderInfo + }; setupWithPackages(packages); -; + // Start with the setting pointing to the invalid package + mTestSystemImpl.updateUserSetting(null, oldSdkPackage.packageName); + mTestSystemImpl.setPackageInfo(newSdkPackage); mTestSystemImpl.setPackageInfo(currentSdkPackage); mTestSystemImpl.setPackageInfo(oldSdkPackage); @@ -1467,4 +1523,74 @@ public class WebViewUpdateServiceTest { assertEquals( defaultPackage1, mWebViewUpdateServiceImpl.getDefaultWebViewPackage().packageName); } + + @Test + @RequiresFlagsEnabled("android.webkit.update_service_v2") + public void testDefaultWebViewPackageEnabling() { + String testPackage = "testDefault"; + WebViewProviderInfo[] packages = + new WebViewProviderInfo[] { + new WebViewProviderInfo( + testPackage, + "", + true /* default available */, + false /* fallback */, + null) + }; + setupWithPackages(packages); + mTestSystemImpl.setPackageInfo( + createPackageInfo( + testPackage, false /* enabled */, true /* valid */, true /* installed */)); + + // Check that the boot time logic re-enables the default package. + runWebViewBootPreparationOnMainSync(); + Mockito.verify(mTestSystemImpl) + .enablePackageForAllUsers( + Matchers.anyObject(), Mockito.eq(testPackage), Mockito.eq(true)); + } + + private void testDefaultPackageChosen(PackageInfo packageInfo) { + WebViewProviderInfo[] packages = + new WebViewProviderInfo[] { + new WebViewProviderInfo(packageInfo.packageName, "", true, false, null) + }; + setupWithPackages(packages); + mTestSystemImpl.setPackageInfo(packageInfo); + + runWebViewBootPreparationOnMainSync(); + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + + assertEquals( + packageInfo.packageName, + mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName); + + WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(packageInfo.packageName, response.packageInfo.packageName); + } + + @Test + @RequiresFlagsEnabled("android.webkit.update_service_v2") + public void testDisabledDefaultPackageChosen() { + PackageInfo disabledPackage = + createPackageInfo( + "disabledPackage", + false /* enabled */, + true /* valid */, + true /* installed */); + + testDefaultPackageChosen(disabledPackage); + } + + @Test + @RequiresFlagsEnabled("android.webkit.update_service_v2") + public void testUninstalledDefaultPackageChosen() { + PackageInfo uninstalledPackage = + createPackageInfo( + "uninstalledPackage", + true /* enabled */, + true /* valid */, + false /* installed */); + + testDefaultPackageChosen(uninstalledPackage); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 9bb2da0ff70c..ba7b52e368f3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -3365,7 +3365,7 @@ public class ActivityRecordTests extends WindowTestsBase { assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput); if (Flags.bundleClientTransactionFlag()) { - verify(app2.getProcess()).scheduleClientTransactionItem( + verify(app2.getProcess(), atLeastOnce()).scheduleClientTransactionItem( isA(WindowStateResizeItem.class)); } else { verify(app2.mClient, atLeastOnce()).resized(any(), anyBoolean(), any(), 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 339162a02301..7c1602b3cae4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java @@ -150,6 +150,8 @@ public class SurfaceAnimationRunnerTest { @FlakyTest(bugId = 71719744) @Test public void testCancel_sneakyCancelBeforeUpdate() throws Exception { + final CountDownLatch animationCancelled = new CountDownLatch(1); + mSurfaceAnimationRunner = new SurfaceAnimationRunner(null, () -> new ValueAnimator() { { setFloatValues(0f, 1f); @@ -162,6 +164,7 @@ public class SurfaceAnimationRunnerTest { // interleaving of multiple threads. Muahahaha if (animation.getCurrentPlayTime() > 0) { mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface); + animationCancelled.countDown(); } listener.onAnimationUpdate(animation); }); @@ -170,11 +173,7 @@ public class SurfaceAnimationRunnerTest { when(mMockAnimationSpec.getDuration()).thenReturn(200L); mSurfaceAnimationRunner.startAnimation(mMockAnimationSpec, mMockSurface, mMockTransaction, this::finishedCallback); - - // We need to wait for two frames: The first frame starts the animation, the second frame - // actually cancels the animation. - waitUntilNextFrame(); - waitUntilNextFrame(); + assertTrue(animationCancelled.await(1, SECONDS)); assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); verify(mMockAnimationSpec, atLeastOnce()).apply(any(), any(), eq(0L)); } diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk b/tests/Camera2Tests/CameraToo/tests/Android.bp index c084849a9d28..8339a2c8ab25 100644 --- a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk +++ b/tests/Camera2Tests/CameraToo/tests/Android.bp @@ -1,5 +1,4 @@ -// -// Copyright (C) 2019 The Android Open Source Project +// 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. @@ -12,20 +11,24 @@ // 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. -// -LOCAL_PATH := $(call my-dir) +package { + // See: http://go/android-license-faq + default_applicable_licenses: [ + "frameworks_base_license", + ], +} -include $(CLEAR_VARS) -LOCAL_USE_AAPT2 := true -LOCAL_AAPT_NAMESPACES := true -LOCAL_MODULE := AaptTestMergeOnly_LeafLib -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE -LOCAL_SDK_VERSION := current -LOCAL_MODULE_TAGS := tests -LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -LOCAL_MIN_SDK_VERSION := 21 -LOCAL_AAPT_FLAGS := --merge-only -include $(BUILD_STATIC_JAVA_LIBRARY) +android_test { + name: "CameraTooTests", + instrumentation_for: "CameraToo", + srcs: ["src/**/*.java"], + sdk_version: "current", + static_libs: [ + "androidx.test.rules", + "mockito-target-minus-junit4", + ], + data: [ + ":CameraToo", + ], +} diff --git a/tests/Camera2Tests/CameraToo/tests/Android.mk b/tests/Camera2Tests/CameraToo/tests/Android.mk deleted file mode 100644 index dfa64f1feade..000000000000 --- a/tests/Camera2Tests/CameraToo/tests/Android.mk +++ /dev/null @@ -1,28 +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. - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests -LOCAL_PACKAGE_NAME := CameraTooTests -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../NOTICE -LOCAL_INSTRUMENTATION_FOR := CameraToo -LOCAL_SDK_VERSION := current -LOCAL_SRC_FILES := $(call all-java-files-under,src) -LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules mockito-target-minus-junit4 - -include $(BUILD_PACKAGE) diff --git a/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml b/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml new file mode 100644 index 000000000000..884c095fef49 --- /dev/null +++ b/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 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. +--> +<configuration description="Runs CameraToo tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="CameraTooTests.apk" /> + <option name="test-file-name" value="CameraToo.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.example.android.camera2.cameratoo.tests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + </test> +</configuration> diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt index bbd4567a4454..7343ba1c1ce7 100644 --- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt @@ -16,6 +16,7 @@ package com.android.server.input +import android.app.NotificationManager import android.content.Context import android.content.ContextWrapper import android.content.pm.ActivityInfo @@ -129,6 +130,8 @@ class KeyboardLayoutManagerTests { @Mock private lateinit var packageManager: PackageManager + @Mock + private lateinit var notificationManager: NotificationManager private lateinit var keyboardLayoutManager: KeyboardLayoutManager private lateinit var imeInfo: InputMethodInfo @@ -163,6 +166,8 @@ class KeyboardLayoutManagerTests { keyboardLayoutManager = Mockito.spy( KeyboardLayoutManager(context, native, dataStore, testLooper.looper) ) + Mockito.`when`(context.getSystemService(Mockito.eq(Context.NOTIFICATION_SERVICE))) + .thenReturn(notificationManager) setupInputDevices() setupBroadcastReceiver() setupIme() @@ -946,6 +951,48 @@ class KeyboardLayoutManagerTests { } } + @Test + fun testNotificationShown_onInputDeviceChanged() { + val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype())) + Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping + Mockito.doReturn(false).`when`(keyboardLayoutManager).isVirtualDevice( + ArgumentMatchers.eq(keyboardDevice.id) + ) + NewSettingsApiFlag(true).use { + keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id) + ExtendedMockito.verify( + notificationManager, + Mockito.times(1) + ).notifyAsUser( + ArgumentMatchers.isNull(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.any(), + ArgumentMatchers.any() + ) + } + } + + @Test + fun testNotificationNotShown_onInputDeviceChanged_forVirtualDevice() { + val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype())) + Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping + Mockito.doReturn(true).`when`(keyboardLayoutManager).isVirtualDevice( + ArgumentMatchers.eq(keyboardDevice.id) + ) + NewSettingsApiFlag(true).use { + keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id) + ExtendedMockito.verify( + notificationManager, + Mockito.never() + ).notifyAsUser( + ArgumentMatchers.isNull(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.any(), + ArgumentMatchers.any() + ) + } + } + private fun assertCorrectLayout( device: InputDevice, imeSubtype: InputMethodSubtype, diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk deleted file mode 100644 index 6361f9b8ae7d..000000000000 --- a/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk +++ /dev/null @@ -1,2 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk deleted file mode 100644 index 27b6068632f3..000000000000 --- a/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk +++ /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. -// - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) -LOCAL_USE_AAPT2 := true -LOCAL_AAPT_NAMESPACES := true -LOCAL_PACKAGE_NAME := AaptTestMergeOnly_App -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE -LOCAL_SDK_VERSION := current -LOCAL_EXPORT_PACKAGE_RESOURCES := true -LOCAL_MODULE_TAGS := tests -LOCAL_STATIC_ANDROID_LIBRARIES := \ - AaptTestMergeOnly_LeafLib \ - AaptTestMergeOnly_LocalLib -include $(BUILD_PACKAGE) diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk deleted file mode 100644 index 699ad79ecf1a..000000000000 --- a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk +++ /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. -// - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) -LOCAL_USE_AAPT2 := true -LOCAL_AAPT_NAMESPACES := true -LOCAL_MODULE := AaptTestMergeOnly_LocalLib -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE -LOCAL_SDK_VERSION := current -LOCAL_MODULE_TAGS := tests -LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -LOCAL_MIN_SDK_VERSION := 21 -LOCAL_AAPT_FLAGS := --merge-only -include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/tools/aapt2/integration-tests/NamespaceTest/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/Android.mk deleted file mode 100644 index 6361f9b8ae7d..000000000000 --- a/tools/aapt2/integration-tests/NamespaceTest/Android.mk +++ /dev/null @@ -1,2 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk deleted file mode 100644 index 98b74403a7ff..000000000000 --- a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright (C) 2017 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. -# - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) -LOCAL_USE_AAPT2 := true -LOCAL_AAPT_NAMESPACES := true -LOCAL_PACKAGE_NAME := AaptTestNamespace_App -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE -LOCAL_SDK_VERSION := current -LOCAL_EXPORT_PACKAGE_RESOURCES := true -LOCAL_MODULE_TAGS := tests -LOCAL_SRC_FILES := $(call all-java-files-under,src) -LOCAL_STATIC_ANDROID_LIBRARIES := \ - AaptTestNamespace_LibOne \ - AaptTestNamespace_LibTwo -include $(BUILD_PACKAGE) diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk deleted file mode 100644 index dd4170234258..000000000000 --- a/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright (C) 2017 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. -# - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) -LOCAL_USE_AAPT2 := true -LOCAL_AAPT_NAMESPACES := true -LOCAL_MODULE := AaptTestNamespace_LibOne -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE -LOCAL_SDK_VERSION := current -LOCAL_MODULE_TAGS := tests -LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -LOCAL_MIN_SDK_VERSION := 21 - -# We need this to retain the R.java generated for this library. -LOCAL_JAR_EXCLUDE_FILES := none -include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk deleted file mode 100644 index 0d11bcbda64d..000000000000 --- a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk +++ /dev/null @@ -1,34 +0,0 @@ -# -# Copyright (C) 2017 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. -# - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) -LOCAL_USE_AAPT2 := true -LOCAL_AAPT_NAMESPACES := true -LOCAL_MODULE := AaptTestNamespace_LibTwo -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE -LOCAL_SDK_VERSION := current -LOCAL_MODULE_TAGS := tests -LOCAL_SRC_FILES := $(call all-java-files-under,src) -LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -LOCAL_MIN_SDK_VERSION := 21 - -# We need this to retain the R.java generated for this library. -LOCAL_JAR_EXCLUDE_FILES := none -include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk deleted file mode 100644 index 30375728c9e0..000000000000 --- a/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk +++ /dev/null @@ -1,32 +0,0 @@ -# -# Copyright (C) 2017 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. -# - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) -LOCAL_USE_AAPT2 := true -LOCAL_AAPT_NAMESPACES := true -LOCAL_PACKAGE_NAME := AaptTestNamespace_Split -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE -LOCAL_SDK_VERSION := current -LOCAL_MODULE_TAGS := tests -LOCAL_SRC_FILES := $(call all-java-files-under,src) -LOCAL_APK_LIBRARIES := AaptTestNamespace_App -LOCAL_RES_LIBRARIES := AaptTestNamespace_App -LOCAL_AAPT_FLAGS := --package-id 0x80 --rename-manifest-package com.android.aapt.namespace.app -include $(BUILD_PACKAGE) |