summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ResourcesManager.java273
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java20
-rw-r--r--core/java/com/android/internal/app/ChooserActivityLogger.java6
-rw-r--r--core/res/res/values/ids.xml3
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java48
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java13
-rw-r--r--data/keyboards/Generic.kl8
-rw-r--r--libs/hwui/service/GraphicsStatsService.cpp1
-rw-r--r--libs/input/Android.bp3
-rw-r--r--libs/input/MouseCursorController.cpp460
-rw-r--r--libs/input/MouseCursorController.h111
-rw-r--r--libs/input/PointerController.cpp758
-rw-r--r--libs/input/PointerController.h165
-rw-r--r--libs/input/PointerControllerContext.cpp179
-rw-r--r--libs/input/PointerControllerContext.h154
-rw-r--r--libs/input/TouchSpotController.cpp236
-rw-r--r--libs/input/TouchSpotController.h91
-rw-r--r--libs/input/tests/PointerController_test.cpp6
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicyConfig.java28
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java11
-rw-r--r--services/core/java/com/android/server/VibratorService.java57
-rwxr-xr-xservices/core/java/com/android/server/audio/AudioService.java24
-rw-r--r--services/core/java/com/android/server/slice/SliceManagerService.java74
-rw-r--r--services/core/jni/com_android_server_VibratorService.cpp30
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp8
-rw-r--r--services/tests/servicestests/src/com/android/server/VibratorServiceTest.java130
27 files changed, 1873 insertions, 1025 deletions
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 747789901b9d..07a6b987e7d3 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -39,7 +39,6 @@ import android.os.Trace;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.LruCache;
import android.util.Pair;
import android.util.Slog;
import android.view.Display;
@@ -62,7 +61,6 @@ import java.util.List;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.function.Consumer;
-import java.util.function.Predicate;
/** @hide */
public class ResourcesManager {
@@ -129,17 +127,30 @@ public class ResourcesManager {
}
}
- private static final boolean ENABLE_APK_ASSETS_CACHE = false;
-
/**
- * The ApkAssets we are caching and intend to hold strong references to.
+ * Loads {@link ApkAssets} and caches them to prevent their garbage collection while the
+ * instance is alive and reachable.
*/
- private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets =
- (ENABLE_APK_ASSETS_CACHE) ? new LruCache<>(3) : null;
+ private class ApkAssetsSupplier {
+ final ArrayMap<ApkKey, ApkAssets> mLocalCache = new ArrayMap<>();
+
+ /**
+ * Retrieves the {@link ApkAssets} corresponding to the specified key, caches the ApkAssets
+ * within this instance, and inserts the loaded ApkAssets into the {@link #mCachedApkAssets}
+ * cache.
+ */
+ ApkAssets load(final ApkKey apkKey) throws IOException {
+ ApkAssets apkAssets = mLocalCache.get(apkKey);
+ if (apkAssets == null) {
+ apkAssets = loadApkAssets(apkKey);
+ mLocalCache.put(apkKey, apkAssets);
+ }
+ return apkAssets;
+ }
+ }
/**
- * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't
- * in our LRU cache. Bonus resources :)
+ * The ApkAssets that are being referenced in the wild that we can reuse.
*/
private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
@@ -337,113 +348,116 @@ public class ResourcesManager {
return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap";
}
- private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay)
- throws IOException {
- final ApkKey newKey = new ApkKey(path, sharedLib, overlay);
- ApkAssets apkAssets = null;
- if (mLoadedApkAssets != null) {
- apkAssets = mLoadedApkAssets.get(newKey);
- if (apkAssets != null && apkAssets.isUpToDate()) {
- return apkAssets;
- }
- }
+ private @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException {
+ ApkAssets apkAssets;
// Optimistically check if this ApkAssets exists somewhere else.
- final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
- if (apkAssetsRef != null) {
- apkAssets = apkAssetsRef.get();
- if (apkAssets != null && apkAssets.isUpToDate()) {
- if (mLoadedApkAssets != null) {
- mLoadedApkAssets.put(newKey, apkAssets);
+ synchronized (this) {
+ final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(key);
+ if (apkAssetsRef != null) {
+ apkAssets = apkAssetsRef.get();
+ if (apkAssets != null && apkAssets.isUpToDate()) {
+ return apkAssets;
+ } else {
+ // Clean up the reference.
+ mCachedApkAssets.remove(key);
}
-
- return apkAssets;
- } else {
- // Clean up the reference.
- mCachedApkAssets.remove(newKey);
}
}
// We must load this from disk.
- if (overlay) {
- apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path), 0 /*flags*/);
+ if (key.overlay) {
+ apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(key.path),
+ 0 /*flags*/);
} else {
- apkAssets = ApkAssets.loadFromPath(path, sharedLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
+ apkAssets = ApkAssets.loadFromPath(key.path,
+ key.sharedLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
}
- if (mLoadedApkAssets != null) {
- mLoadedApkAssets.put(newKey, apkAssets);
+ synchronized (this) {
+ mCachedApkAssets.put(key, new WeakReference<>(apkAssets));
}
- mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets));
return apkAssets;
}
/**
- * Creates an AssetManager from the paths within the ResourcesKey.
- *
- * This can be overridden in tests so as to avoid creating a real AssetManager with
- * real APK paths.
- * @param key The key containing the resource paths to add to the AssetManager.
- * @return a new AssetManager.
- */
- @VisibleForTesting
- @UnsupportedAppUsage
- protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
- final AssetManager.Builder builder = new AssetManager.Builder();
+ * Retrieves a list of apk keys representing the ApkAssets that should be loaded for
+ * AssetManagers mapped to the {@param key}.
+ */
+ private static @NonNull ArrayList<ApkKey> extractApkKeys(@NonNull final ResourcesKey key) {
+ final ArrayList<ApkKey> apkKeys = new ArrayList<>();
// resDir can be null if the 'android' package is creating a new Resources object.
// This is fine, since each AssetManager automatically loads the 'android' package
// already.
if (key.mResDir != null) {
- try {
- builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,
- false /*overlay*/));
- } catch (IOException e) {
- Log.e(TAG, "failed to add asset path " + key.mResDir);
- return null;
- }
+ apkKeys.add(new ApkKey(key.mResDir, false /*sharedLib*/, false /*overlay*/));
}
if (key.mSplitResDirs != null) {
for (final String splitResDir : key.mSplitResDirs) {
- try {
- builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/,
- false /*overlay*/));
- } catch (IOException e) {
- Log.e(TAG, "failed to add split asset path " + splitResDir);
- return null;
- }
+ apkKeys.add(new ApkKey(splitResDir, false /*sharedLib*/, false /*overlay*/));
}
}
if (key.mLibDirs != null) {
for (final String libDir : key.mLibDirs) {
+ // Avoid opening files we know do not have resources, like code-only .jar files.
if (libDir.endsWith(".apk")) {
- // Avoid opening files we know do not have resources,
- // like code-only .jar files.
- try {
- builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/,
- false /*overlay*/));
- } catch (IOException e) {
- Log.w(TAG, "Asset path '" + libDir +
- "' does not exist or contains no resources.");
-
- // continue.
- }
+ apkKeys.add(new ApkKey(libDir, true /*sharedLib*/, false /*overlay*/));
}
}
}
if (key.mOverlayDirs != null) {
for (final String idmapPath : key.mOverlayDirs) {
- try {
- builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
- true /*overlay*/));
- } catch (IOException e) {
- Log.w(TAG, "failed to add overlay path " + idmapPath);
+ apkKeys.add(new ApkKey(idmapPath, false /*sharedLib*/, true /*overlay*/));
+ }
+ }
+
+ return apkKeys;
+ }
+
+ /**
+ * Creates an AssetManager from the paths within the ResourcesKey.
+ *
+ * This can be overridden in tests so as to avoid creating a real AssetManager with
+ * real APK paths.
+ * @param key The key containing the resource paths to add to the AssetManager.
+ * @return a new AssetManager.
+ */
+ @VisibleForTesting
+ @UnsupportedAppUsage
+ protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
+ return createAssetManager(key, /* apkSupplier */ null);
+ }
+
+ /**
+ * Variant of {@link #createAssetManager(ResourcesKey)} that attempts to load ApkAssets
+ * from an {@link ApkAssetsSupplier} if non-null; otherwise ApkAssets are loaded using
+ * {@link #loadApkAssets(ApkKey)}.
+ */
+ private @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key,
+ @Nullable ApkAssetsSupplier apkSupplier) {
+ final AssetManager.Builder builder = new AssetManager.Builder();
- // continue.
+ final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
+ for (int i = 0, n = apkKeys.size(); i < n; i++) {
+ final ApkKey apkKey = apkKeys.get(i);
+ try {
+ builder.addApkAssets(
+ (apkSupplier != null) ? apkSupplier.load(apkKey) : loadApkAssets(apkKey));
+ } catch (IOException e) {
+ if (apkKey.overlay) {
+ Log.w(TAG, String.format("failed to add overlay path '%s'", apkKey.path), e);
+ } else if (apkKey.sharedLib) {
+ Log.w(TAG, String.format(
+ "asset path '%s' does not exist or contains no resources",
+ apkKey.path), e);
+ } else {
+ Log.e(TAG, String.format("failed to add asset path '%s'", apkKey.path), e);
+ return null;
}
}
}
@@ -480,24 +494,6 @@ public class ResourcesManager {
pw.println("ResourcesManager:");
pw.increaseIndent();
- if (mLoadedApkAssets != null) {
- pw.print("cached apks: total=");
- pw.print(mLoadedApkAssets.size());
- pw.print(" created=");
- pw.print(mLoadedApkAssets.createCount());
- pw.print(" evicted=");
- pw.print(mLoadedApkAssets.evictionCount());
- pw.print(" hit=");
- pw.print(mLoadedApkAssets.hitCount());
- pw.print(" miss=");
- pw.print(mLoadedApkAssets.missCount());
- pw.print(" max=");
- pw.print(mLoadedApkAssets.maxSize());
- } else {
- pw.print("cached apks: 0 [cache disabled]");
- }
- pw.println();
-
pw.print("total apks: ");
pw.println(countLiveReferences(mCachedApkAssets.values()));
@@ -533,11 +529,12 @@ public class ResourcesManager {
return config;
}
- private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
+ private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
+ @Nullable ApkAssetsSupplier apkSupplier) {
final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
daj.setCompatibilityInfo(key.mCompatInfo);
- final AssetManager assets = createAssetManager(key);
+ final AssetManager assets = createAssetManager(key, apkSupplier);
if (assets == null) {
return null;
}
@@ -575,9 +572,18 @@ public class ResourcesManager {
*/
private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
@NonNull ResourcesKey key) {
+ return findOrCreateResourcesImplForKeyLocked(key, /* apkSupplier */ null);
+ }
+
+ /**
+ * Variant of {@link #findOrCreateResourcesImplForKeyLocked(ResourcesKey)} that attempts to
+ * load ApkAssets from a {@link ApkAssetsSupplier} when creating a new ResourcesImpl.
+ */
+ private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
+ @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) {
ResourcesImpl impl = findResourcesImplForKeyLocked(key);
if (impl == null) {
- impl = createResourcesImpl(key);
+ impl = createResourcesImpl(key, apkSupplier);
if (impl != null) {
mResourceImpls.put(key, new WeakReference<>(impl));
}
@@ -766,7 +772,7 @@ public class ResourcesManager {
}
// Now request an actual Resources object.
- return createResources(token, key, classLoader);
+ return createResources(token, key, classLoader, /* apkSupplier */ null);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
@@ -810,18 +816,50 @@ public class ResourcesManager {
}
/**
+ * Creates an {@link ApkAssetsSupplier} and loads all the ApkAssets required by the {@param key}
+ * into the supplier. This should be done while the lock is not held to prevent performing I/O
+ * while holding the lock.
+ */
+ private @NonNull ApkAssetsSupplier createApkAssetsSupplierNotLocked(@NonNull ResourcesKey key) {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
+ "ResourcesManager#createApkAssetsSupplierNotLocked");
+ try {
+ if (Thread.holdsLock(this)) {
+ Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+ + " is holding mLock", new Throwable());
+ }
+
+ final ApkAssetsSupplier supplier = new ApkAssetsSupplier();
+ final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
+ for (int i = 0, n = apkKeys.size(); i < n; i++) {
+ final ApkKey apkKey = apkKeys.get(i);
+ try {
+ supplier.load(apkKey);
+ } catch (IOException e) {
+ Log.w(TAG, String.format("failed to preload asset path '%s'", apkKey.path), e);
+ }
+ }
+ return supplier;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ }
+
+ /**
* Creates a Resources object set with a ResourcesImpl object matching the given key.
*
* @param activityToken The Activity this Resources object should be associated with.
* @param key The key describing the parameters of the ResourcesImpl object.
* @param classLoader The classloader to use for the Resources object.
* If null, {@link ClassLoader#getSystemClassLoader()} is used.
+ * @param apkSupplier The apk assets supplier to use when creating a new ResourcesImpl object.
* @return A Resources object that gets updated when
* {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
* is called.
*/
private @Nullable Resources createResources(@Nullable IBinder activityToken,
- @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
+ @NonNull ResourcesKey key, @NonNull ClassLoader classLoader,
+ @Nullable ApkAssetsSupplier apkSupplier) {
synchronized (this) {
if (DEBUG) {
Throwable here = new Throwable();
@@ -829,7 +867,7 @@ public class ResourcesManager {
Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
}
- ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key);
+ ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
if (resourcesImpl == null) {
return null;
}
@@ -898,7 +936,10 @@ public class ResourcesManager {
rebaseKeyForActivity(activityToken, key);
}
- return createResources(activityToken, key, classLoader);
+ // Preload the ApkAssets required by the key to prevent performing heavy I/O while the
+ // ResourcesManager lock is held.
+ final ApkAssetsSupplier assetsSupplier = createApkAssetsSupplierNotLocked(key);
+ return createResources(activityToken, key, classLoader, assetsSupplier);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
@@ -969,7 +1010,13 @@ public class ResourcesManager {
final ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
overrideConfig, displayId);
if (newKey != null) {
- updateActivityResources(resources, newKey, false);
+ final ResourcesImpl resourcesImpl =
+ findOrCreateResourcesImplForKeyLocked(newKey);
+ if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
+ // Set the ResourcesImpl, updating it for all users of this Resources
+ // object.
+ resources.setImpl(resourcesImpl);
+ }
}
}
}
@@ -1024,24 +1071,6 @@ public class ResourcesManager {
return newKey;
}
- private void updateActivityResources(Resources resources, ResourcesKey newKey,
- boolean hasLoader) {
- final ResourcesImpl resourcesImpl;
-
- if (hasLoader) {
- // Loaders always get new Impls because they cannot be shared
- resourcesImpl = createResourcesImpl(newKey);
- } else {
- resourcesImpl = findOrCreateResourcesImplForKeyLocked(newKey);
- }
-
- if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
- // Set the ResourcesImpl, updating it for all users of this Resources
- // object.
- resources.setImpl(resourcesImpl);
- }
- }
-
public final boolean applyConfigurationToResources(@NonNull Configuration config,
@Nullable CompatibilityInfo compat) {
synchronized(this) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index fe774780c133..fd90b56426aa 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -206,6 +206,7 @@ public class ChooserActivity extends ResolverActivity implements
public static final int SELECTION_TYPE_APP = 2;
public static final int SELECTION_TYPE_STANDARD = 3;
public static final int SELECTION_TYPE_COPY = 4;
+ public static final int SELECTION_TYPE_NEARBY = 5;
private static final int SCROLL_STATUS_IDLE = 0;
private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1;
@@ -1135,7 +1136,8 @@ public class ChooserActivity extends ResolverActivity implements
return displayContentPreview(previewType, targetIntent, getLayoutInflater(), parent);
}
- private ComponentName getNearbySharingComponent() {
+ @VisibleForTesting
+ protected ComponentName getNearbySharingComponent() {
String nearbyComponent = Settings.Secure.getString(
getContentResolver(),
Settings.Secure.NEARBY_SHARING_COMPONENT);
@@ -1148,7 +1150,8 @@ public class ChooserActivity extends ResolverActivity implements
return ComponentName.unflattenFromString(nearbyComponent);
}
- private TargetInfo getNearbySharingTarget(Intent originalIntent) {
+ @VisibleForTesting
+ protected TargetInfo getNearbySharingTarget(Intent originalIntent) {
final ComponentName cn = getNearbySharingComponent();
if (cn == null) return null;
@@ -1216,14 +1219,21 @@ public class ChooserActivity extends ResolverActivity implements
final TargetInfo ti = getNearbySharingTarget(originalIntent);
if (ti == null) return null;
- return createActionButton(
+ final Button b = createActionButton(
ti.getDisplayIcon(this),
ti.getDisplayLabel(),
(View unused) -> {
+ // Log share completion via nearby
+ getChooserActivityLogger().logShareTargetSelected(
+ SELECTION_TYPE_NEARBY,
+ "",
+ -1);
safelyStartActivity(ti);
finish();
}
);
+ b.setId(R.id.chooser_nearby_button);
+ return b;
}
private void addActionButton(ViewGroup parent, Button b) {
@@ -2616,7 +2626,9 @@ public class ChooserActivity extends ResolverActivity implements
}
}
- static final class EmptyTargetInfo extends NotSelectableTargetInfo {
+ protected static final class EmptyTargetInfo extends NotSelectableTargetInfo {
+ public EmptyTargetInfo() {}
+
public Drawable getDisplayIcon(Context context) {
return null;
}
diff --git a/core/java/com/android/internal/app/ChooserActivityLogger.java b/core/java/com/android/internal/app/ChooserActivityLogger.java
index c26bac437915..426859e1d527 100644
--- a/core/java/com/android/internal/app/ChooserActivityLogger.java
+++ b/core/java/com/android/internal/app/ChooserActivityLogger.java
@@ -116,7 +116,9 @@ public interface ChooserActivityLogger {
@UiEvent(doc = "User selected a standard target.")
SHARESHEET_STANDARD_TARGET_SELECTED(234),
@UiEvent(doc = "User selected the copy target.")
- SHARESHEET_COPY_TARGET_SELECTED(235);
+ SHARESHEET_COPY_TARGET_SELECTED(235),
+ @UiEvent(doc = "User selected the nearby target.")
+ SHARESHEET_NEARBY_TARGET_SELECTED(626);
private final int mId;
SharesheetTargetSelectedEvent(int id) {
@@ -136,6 +138,8 @@ public interface ChooserActivityLogger {
return SHARESHEET_STANDARD_TARGET_SELECTED;
case ChooserActivity.SELECTION_TYPE_COPY:
return SHARESHEET_COPY_TARGET_SELECTED;
+ case ChooserActivity.SELECTION_TYPE_NEARBY:
+ return SHARESHEET_NEARBY_TARGET_SELECTED;
default:
return INVALID;
}
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index bddda1bf6f6f..f77c6f99c063 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -197,6 +197,9 @@
<!-- Marks the "copy to clipboard" button in the ChooserActivity -->
<item type="id" name="chooser_copy_button" />
+ <!-- Marks the "nearby" button in the ChooserActivity -->
+ <item type="id" name="chooser_nearby_button" />
+
<!-- Accessibility action identifier for {@link android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK}. -->
<item type="id" name="accessibilitySystemActionBack" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d5c72da2d29f..040fad5e6410 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3841,6 +3841,7 @@
<java-symbol type="layout" name="chooser_dialog_item" />
<java-symbol type="drawable" name="chooser_dialog_background" />
<java-symbol type="id" name="chooser_copy_button" />
+ <java-symbol type="id" name="chooser_nearby_button" />
<java-symbol type="layout" name="chooser_action_button" />
<java-symbol type="dimen" name="chooser_action_button_icon_size" />
<java-symbol type="string" name="config_defaultNearbySharingComponent" />
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 090645f6f7a8..787879a6a144 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -590,6 +590,54 @@ public class ChooserActivityTest {
is(1));
}
+
+ @Test
+ public void testNearbyShareLogging() throws Exception {
+ Intent sendIntent = createSendTextIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+ when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ final ChooserWrapperActivity activity = mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+ waitForIdle();
+
+ onView(withId(R.id.chooser_nearby_button)).check(matches(isDisplayed()));
+ onView(withId(R.id.chooser_nearby_button)).perform(click());
+
+ ChooserActivityLoggerFake logger =
+ (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+ assertThat(logger.numCalls(), is(6));
+ // first one should be SHARESHEET_TRIGGERED uievent
+ assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(0).event.getId(),
+ is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
+ // second one should be SHARESHEET_STARTED event
+ assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
+ assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
+ assertThat(logger.get(1).mimeType, is("text/plain"));
+ assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+ assertThat(logger.get(1).appProvidedApp, is(0));
+ assertThat(logger.get(1).appProvidedDirect, is(0));
+ assertThat(logger.get(1).isWorkprofile, is(false));
+ assertThat(logger.get(1).previewType, is(3));
+ // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+ assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(2).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+ // fourth and fifth are just artifacts of test set-up
+ // sixth one should be ranking atom with SHARESHEET_NEARBY_TARGET_SELECTED event
+ assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+ assertThat(logger.get(5).targetType,
+ is(ChooserActivityLogger
+ .SharesheetTargetSelectedEvent.SHARESHEET_NEARBY_TARGET_SELECTED.getId()));
+ }
+
+
@Test
public void oneVisibleImagePreview() throws InterruptedException {
Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index d3d5caf3f7e2..16a2fbd6465e 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -21,6 +21,7 @@ import static org.mockito.Mockito.when;
import android.annotation.Nullable;
import android.app.usage.UsageStatsManager;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -89,6 +90,18 @@ public class ChooserWrapperActivity extends ChooserActivity {
boolean getIsSelected() { return mIsSuccessfullySelected; }
+ @Override
+ protected ComponentName getNearbySharingComponent() {
+ // an arbitrary pre-installed activity that handles this type of intent
+ return ComponentName.unflattenFromString("com.google.android.apps.messaging/"
+ + "com.google.android.apps.messaging.ui.conversationlist.ShareIntentActivity");
+ }
+
+ @Override
+ protected TargetInfo getNearbySharingTarget(Intent originalIntent) {
+ return new ChooserWrapperActivity.EmptyTargetInfo();
+ }
+
UsageStatsManager getUsageStatsManager() {
if (mUsm == null) {
mUsm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 5711f98e3b75..bd2d4af54c83 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -345,10 +345,10 @@ key 377 TV
# key 395 "KEY_LIST"
# key 396 "KEY_MEMO"
key 397 CALENDAR
-# key 398 "KEY_RED"
-# key 399 "KEY_GREEN"
-# key 400 "KEY_YELLOW"
-# key 401 "KEY_BLUE"
+key 398 PROG_RED
+key 399 PROG_GREEN
+key 400 PROG_YELLOW
+key 401 PROG_BLUE
key 402 CHANNEL_UP
key 403 CHANNEL_DOWN
# key 404 "KEY_FIRST"
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 644d5fbd5bf9..e4198017aee0 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -559,6 +559,7 @@ void GraphicsStatsService::finishDumpInMemory(Dump* dump, AStatsEventList* data,
AStatsEvent_writeBool(event, !lastFullDay);
AStatsEvent_build(event);
}
+ delete dump;
}
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 5252cd041199..dca35012cbdd 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -16,6 +16,9 @@ cc_library_shared {
name: "libinputservice",
srcs: [
"PointerController.cpp",
+ "PointerControllerContext.cpp",
+ "MouseCursorController.cpp",
+ "TouchSpotController.cpp",
"SpriteController.cpp",
"SpriteIcon.cpp",
],
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
new file mode 100644
index 000000000000..80b555be97dd
--- /dev/null
+++ b/libs/input/MouseCursorController.cpp
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MouseCursorController"
+//#define LOG_NDEBUG 0
+
+// Log debug messages about pointer updates
+#define DEBUG_MOUSE_CURSOR_UPDATES 0
+
+#include "MouseCursorController.h"
+
+#include <log/log.h>
+
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
+
+namespace {
+// Time to spend fading out the pointer completely.
+const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
+} // namespace
+
+namespace android {
+
+// --- MouseCursorController ---
+
+MouseCursorController::MouseCursorController(PointerControllerContext& context)
+ : mContext(context) {
+ std::scoped_lock lock(mLock);
+
+ mLocked.animationFrameIndex = 0;
+ mLocked.lastFrameUpdatedTime = 0;
+
+ mLocked.pointerFadeDirection = 0;
+ mLocked.pointerX = 0;
+ mLocked.pointerY = 0;
+ mLocked.pointerAlpha = 0.0f; // pointer is initially faded
+ mLocked.pointerSprite = mContext.getSpriteController()->createSprite();
+ mLocked.updatePointerIcon = false;
+ mLocked.requestedPointerType = mContext.getPolicy()->getDefaultPointerIconId();
+
+ mLocked.resourcesLoaded = false;
+
+ mLocked.buttonState = 0;
+}
+
+MouseCursorController::~MouseCursorController() {
+ std::scoped_lock lock(mLock);
+
+ mLocked.pointerSprite.clear();
+}
+
+bool MouseCursorController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
+ float* outMaxY) const {
+ std::scoped_lock lock(mLock);
+
+ return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
+}
+
+bool MouseCursorController::getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX,
+ float* outMaxY) const REQUIRES(mLock) {
+ if (!mLocked.viewport.isValid()) {
+ return false;
+ }
+
+ *outMinX = mLocked.viewport.logicalLeft;
+ *outMinY = mLocked.viewport.logicalTop;
+ *outMaxX = mLocked.viewport.logicalRight - 1;
+ *outMaxY = mLocked.viewport.logicalBottom - 1;
+ return true;
+}
+
+void MouseCursorController::move(float deltaX, float deltaY) {
+#if DEBUG_MOUSE_CURSOR_UPDATES
+ ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
+#endif
+ if (deltaX == 0.0f && deltaY == 0.0f) {
+ return;
+ }
+
+ std::scoped_lock lock(mLock);
+
+ setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
+}
+
+void MouseCursorController::setButtonState(int32_t buttonState) {
+#if DEBUG_MOUSE_CURSOR_UPDATES
+ ALOGD("Set button state 0x%08x", buttonState);
+#endif
+ std::scoped_lock lock(mLock);
+
+ if (mLocked.buttonState != buttonState) {
+ mLocked.buttonState = buttonState;
+ }
+}
+
+int32_t MouseCursorController::getButtonState() const {
+ std::scoped_lock lock(mLock);
+ return mLocked.buttonState;
+}
+
+void MouseCursorController::setPosition(float x, float y) {
+#if DEBUG_MOUSE_CURSOR_UPDATES
+ ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
+#endif
+ std::scoped_lock lock(mLock);
+ setPositionLocked(x, y);
+}
+
+void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
+ float minX, minY, maxX, maxY;
+ if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
+ if (x <= minX) {
+ mLocked.pointerX = minX;
+ } else if (x >= maxX) {
+ mLocked.pointerX = maxX;
+ } else {
+ mLocked.pointerX = x;
+ }
+ if (y <= minY) {
+ mLocked.pointerY = minY;
+ } else if (y >= maxY) {
+ mLocked.pointerY = maxY;
+ } else {
+ mLocked.pointerY = y;
+ }
+ updatePointerLocked();
+ }
+}
+
+void MouseCursorController::getPosition(float* outX, float* outY) const {
+ std::scoped_lock lock(mLock);
+
+ *outX = mLocked.pointerX;
+ *outY = mLocked.pointerY;
+}
+
+int32_t MouseCursorController::getDisplayId() const {
+ std::scoped_lock lock(mLock);
+ return mLocked.viewport.displayId;
+}
+
+void MouseCursorController::fade(PointerControllerInterface::Transition transition) {
+ std::scoped_lock lock(mLock);
+
+ // Remove the inactivity timeout, since we are fading now.
+ mContext.removeInactivityTimeout();
+
+ // Start fading.
+ if (transition == PointerControllerInterface::Transition::IMMEDIATE) {
+ mLocked.pointerFadeDirection = 0;
+ mLocked.pointerAlpha = 0.0f;
+ updatePointerLocked();
+ } else {
+ mLocked.pointerFadeDirection = -1;
+ mContext.startAnimation();
+ }
+}
+
+void MouseCursorController::unfade(PointerControllerInterface::Transition transition) {
+ std::scoped_lock lock(mLock);
+
+ // Always reset the inactivity timer.
+ mContext.resetInactivityTimeout();
+
+ // Start unfading.
+ if (transition == PointerControllerInterface::Transition::IMMEDIATE) {
+ mLocked.pointerFadeDirection = 0;
+ mLocked.pointerAlpha = 1.0f;
+ updatePointerLocked();
+ } else {
+ mLocked.pointerFadeDirection = 1;
+ mContext.startAnimation();
+ }
+}
+
+void MouseCursorController::reloadPointerResources(bool getAdditionalMouseResources) {
+ std::scoped_lock lock(mLock);
+
+ loadResourcesLocked(getAdditionalMouseResources);
+ updatePointerLocked();
+}
+
+/**
+ * The viewport values for deviceHeight and deviceWidth have already been adjusted for rotation,
+ * so here we are getting the dimensions in the original, unrotated orientation (orientation 0).
+ */
+static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, int32_t& height) {
+ width = viewport.deviceWidth;
+ height = viewport.deviceHeight;
+
+ if (viewport.orientation == DISPLAY_ORIENTATION_90 ||
+ viewport.orientation == DISPLAY_ORIENTATION_270) {
+ std::swap(width, height);
+ }
+}
+
+void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport,
+ bool getAdditionalMouseResources) {
+ std::scoped_lock lock(mLock);
+
+ if (viewport == mLocked.viewport) {
+ return;
+ }
+
+ const DisplayViewport oldViewport = mLocked.viewport;
+ mLocked.viewport = viewport;
+
+ int32_t oldDisplayWidth, oldDisplayHeight;
+ getNonRotatedSize(oldViewport, oldDisplayWidth, oldDisplayHeight);
+ int32_t newDisplayWidth, newDisplayHeight;
+ getNonRotatedSize(viewport, newDisplayWidth, newDisplayHeight);
+
+ // Reset cursor position to center if size or display changed.
+ if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth ||
+ oldDisplayHeight != newDisplayHeight) {
+ float minX, minY, maxX, maxY;
+ if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
+ mLocked.pointerX = (minX + maxX) * 0.5f;
+ mLocked.pointerY = (minY + maxY) * 0.5f;
+ // Reload icon resources for density may be changed.
+ loadResourcesLocked(getAdditionalMouseResources);
+ } else {
+ mLocked.pointerX = 0;
+ mLocked.pointerY = 0;
+ }
+ } else if (oldViewport.orientation != viewport.orientation) {
+ // Apply offsets to convert from the pixel top-left corner position to the pixel center.
+ // This creates an invariant frame of reference that we can easily rotate when
+ // taking into account that the pointer may be located at fractional pixel offsets.
+ float x = mLocked.pointerX + 0.5f;
+ float y = mLocked.pointerY + 0.5f;
+ float temp;
+
+ // Undo the previous rotation.
+ switch (oldViewport.orientation) {
+ case DISPLAY_ORIENTATION_90:
+ temp = x;
+ x = oldViewport.deviceHeight - y;
+ y = temp;
+ break;
+ case DISPLAY_ORIENTATION_180:
+ x = oldViewport.deviceWidth - x;
+ y = oldViewport.deviceHeight - y;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ temp = x;
+ x = y;
+ y = oldViewport.deviceWidth - temp;
+ break;
+ }
+
+ // Perform the new rotation.
+ switch (viewport.orientation) {
+ case DISPLAY_ORIENTATION_90:
+ temp = x;
+ x = y;
+ y = viewport.deviceHeight - temp;
+ break;
+ case DISPLAY_ORIENTATION_180:
+ x = viewport.deviceWidth - x;
+ y = viewport.deviceHeight - y;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ temp = x;
+ x = viewport.deviceWidth - y;
+ y = temp;
+ break;
+ }
+
+ // Apply offsets to convert from the pixel center to the pixel top-left corner position
+ // and save the results.
+ mLocked.pointerX = x - 0.5f;
+ mLocked.pointerY = y - 0.5f;
+ }
+
+ updatePointerLocked();
+}
+
+void MouseCursorController::updatePointerIcon(int32_t iconId) {
+ std::scoped_lock lock(mLock);
+
+ if (mLocked.requestedPointerType != iconId) {
+ mLocked.requestedPointerType = iconId;
+ mLocked.updatePointerIcon = true;
+ updatePointerLocked();
+ }
+}
+
+void MouseCursorController::setCustomPointerIcon(const SpriteIcon& icon) {
+ std::scoped_lock lock(mLock);
+
+ const int32_t iconId = mContext.getPolicy()->getCustomPointerIconId();
+ mLocked.additionalMouseResources[iconId] = icon;
+ mLocked.requestedPointerType = iconId;
+ mLocked.updatePointerIcon = true;
+ updatePointerLocked();
+}
+
+bool MouseCursorController::doFadingAnimation(nsecs_t timestamp, bool keepAnimating) {
+ nsecs_t frameDelay = timestamp - mContext.getAnimationTime();
+
+ std::scoped_lock lock(mLock);
+
+ // Animate pointer fade.
+ if (mLocked.pointerFadeDirection < 0) {
+ mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION;
+ if (mLocked.pointerAlpha <= 0.0f) {
+ mLocked.pointerAlpha = 0.0f;
+ mLocked.pointerFadeDirection = 0;
+ } else {
+ keepAnimating = true;
+ }
+ updatePointerLocked();
+ } else if (mLocked.pointerFadeDirection > 0) {
+ mLocked.pointerAlpha += float(frameDelay) / POINTER_FADE_DURATION;
+ if (mLocked.pointerAlpha >= 1.0f) {
+ mLocked.pointerAlpha = 1.0f;
+ mLocked.pointerFadeDirection = 0;
+ } else {
+ keepAnimating = true;
+ }
+ updatePointerLocked();
+ }
+
+ return keepAnimating;
+}
+
+bool MouseCursorController::doBitmapAnimation(nsecs_t timestamp) {
+ std::scoped_lock lock(mLock);
+
+ std::map<int32_t, PointerAnimation>::const_iterator iter =
+ mLocked.animationResources.find(mLocked.requestedPointerType);
+ if (iter == mLocked.animationResources.end()) {
+ return false;
+ }
+
+ if (timestamp - mLocked.lastFrameUpdatedTime > iter->second.durationPerFrame) {
+ sp<SpriteController> spriteController = mContext.getSpriteController();
+ spriteController->openTransaction();
+
+ int incr = (timestamp - mLocked.lastFrameUpdatedTime) / iter->second.durationPerFrame;
+ mLocked.animationFrameIndex += incr;
+ mLocked.lastFrameUpdatedTime += iter->second.durationPerFrame * incr;
+ while (mLocked.animationFrameIndex >= iter->second.animationFrames.size()) {
+ mLocked.animationFrameIndex -= iter->second.animationFrames.size();
+ }
+ mLocked.pointerSprite->setIcon(iter->second.animationFrames[mLocked.animationFrameIndex]);
+
+ spriteController->closeTransaction();
+ }
+
+ // Keep animating.
+ return true;
+}
+
+void MouseCursorController::updatePointerLocked() REQUIRES(mLock) {
+ if (!mLocked.viewport.isValid()) {
+ return;
+ }
+ sp<SpriteController> spriteController = mContext.getSpriteController();
+ spriteController->openTransaction();
+
+ mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
+ mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+ mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId);
+
+ if (mLocked.pointerAlpha > 0) {
+ mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
+ mLocked.pointerSprite->setVisible(true);
+ } else {
+ mLocked.pointerSprite->setVisible(false);
+ }
+
+ if (mLocked.updatePointerIcon) {
+ if (mLocked.requestedPointerType == mContext.getPolicy()->getDefaultPointerIconId()) {
+ mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
+ } else {
+ std::map<int32_t, SpriteIcon>::const_iterator iter =
+ mLocked.additionalMouseResources.find(mLocked.requestedPointerType);
+ if (iter != mLocked.additionalMouseResources.end()) {
+ std::map<int32_t, PointerAnimation>::const_iterator anim_iter =
+ mLocked.animationResources.find(mLocked.requestedPointerType);
+ if (anim_iter != mLocked.animationResources.end()) {
+ mLocked.animationFrameIndex = 0;
+ mLocked.lastFrameUpdatedTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mContext.startAnimation();
+ }
+ mLocked.pointerSprite->setIcon(iter->second);
+ } else {
+ ALOGW("Can't find the resource for icon id %d", mLocked.requestedPointerType);
+ mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
+ }
+ }
+ mLocked.updatePointerIcon = false;
+ }
+
+ spriteController->closeTransaction();
+}
+
+void MouseCursorController::loadResourcesLocked(bool getAdditionalMouseResources) REQUIRES(mLock) {
+ if (!mLocked.viewport.isValid()) {
+ return;
+ }
+
+ if (!mLocked.resourcesLoaded) mLocked.resourcesLoaded = true;
+
+ sp<PointerControllerPolicyInterface> policy = mContext.getPolicy();
+ policy->loadPointerResources(&mResources, mLocked.viewport.displayId);
+ policy->loadPointerIcon(&mLocked.pointerIcon, mLocked.viewport.displayId);
+
+ mLocked.additionalMouseResources.clear();
+ mLocked.animationResources.clear();
+ if (getAdditionalMouseResources) {
+ policy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
+ &mLocked.animationResources,
+ mLocked.viewport.displayId);
+ }
+
+ mLocked.updatePointerIcon = true;
+}
+
+bool MouseCursorController::isViewportValid() {
+ std::scoped_lock lock(mLock);
+ return mLocked.viewport.isValid();
+}
+
+void MouseCursorController::getAdditionalMouseResources() {
+ std::scoped_lock lock(mLock);
+
+ if (mLocked.additionalMouseResources.empty()) {
+ mContext.getPolicy()->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
+ &mLocked.animationResources,
+ mLocked.viewport.displayId);
+ }
+ mLocked.updatePointerIcon = true;
+ updatePointerLocked();
+}
+
+bool MouseCursorController::resourcesLoaded() {
+ std::scoped_lock lock(mLock);
+ return mLocked.resourcesLoaded;
+}
+
+} // namespace android
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
new file mode 100644
index 000000000000..448165b5ac46
--- /dev/null
+++ b/libs/input/MouseCursorController.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_MOUSE_CURSOR_CONTROLLER_H
+#define _UI_MOUSE_CURSOR_CONTROLLER_H
+
+#include <gui/DisplayEventReceiver.h>
+#include <input/DisplayViewport.h>
+#include <input/Input.h>
+#include <ui/DisplayInfo.h>
+#include <utils/BitSet.h>
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "PointerControllerContext.h"
+#include "SpriteController.h"
+
+namespace android {
+
+/*
+ * Helper class for PointerController that specifically handles
+ * mouse cursor resources and actions.
+ */
+class MouseCursorController {
+public:
+ MouseCursorController(PointerControllerContext& context);
+ ~MouseCursorController();
+
+ bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ void move(float deltaX, float deltaY);
+ void setButtonState(int32_t buttonState);
+ int32_t getButtonState() const;
+ void setPosition(float x, float y);
+ void getPosition(float* outX, float* outY) const;
+ int32_t getDisplayId() const;
+ void fade(PointerControllerInterface::Transition transition);
+ void unfade(PointerControllerInterface::Transition transition);
+ void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources);
+
+ void updatePointerIcon(int32_t iconId);
+ void setCustomPointerIcon(const SpriteIcon& icon);
+ void reloadPointerResources(bool getAdditionalMouseResources);
+
+ void getAdditionalMouseResources();
+ bool isViewportValid();
+
+ bool doBitmapAnimation(nsecs_t timestamp);
+ bool doFadingAnimation(nsecs_t timestamp, bool keepAnimating);
+
+ bool resourcesLoaded();
+
+private:
+ mutable std::mutex mLock;
+
+ PointerResources mResources;
+
+ PointerControllerContext& mContext;
+
+ struct Locked {
+ DisplayViewport viewport;
+
+ size_t animationFrameIndex;
+ nsecs_t lastFrameUpdatedTime;
+
+ int32_t pointerFadeDirection;
+ float pointerX;
+ float pointerY;
+ float pointerAlpha;
+ sp<Sprite> pointerSprite;
+ SpriteIcon pointerIcon;
+ bool updatePointerIcon;
+
+ bool resourcesLoaded;
+
+ std::map<int32_t, SpriteIcon> additionalMouseResources;
+ std::map<int32_t, PointerAnimation> animationResources;
+
+ int32_t requestedPointerType;
+
+ int32_t buttonState;
+
+ } mLocked GUARDED_BY(mLock);
+
+ bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ void setPositionLocked(float x, float y);
+
+ void updatePointerLocked();
+
+ void loadResourcesLocked(bool getAdditionalMouseResources);
+};
+
+} // namespace android
+
+#endif // _UI_MOUSE_CURSOR_CONTROLLER_H
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 5e480a66c355..14c96cefd462 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -21,31 +21,26 @@
#define DEBUG_POINTER_UPDATES 0
#include "PointerController.h"
+#include "MouseCursorController.h"
+#include "PointerControllerContext.h"
+#include "TouchSpotController.h"
#include <log/log.h>
-#include <memory>
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
namespace android {
// --- PointerController ---
-// Time to wait before starting the fade when the pointer is inactive.
-static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
-static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
-
-// Time to spend fading out the spot completely.
-static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
-
-// Time to spend fading out the pointer completely.
-static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
-
-// The number of events to be read at once for DisplayEventReceiver.
-static const int EVENT_BUFFER_SIZE = 100;
-
std::shared_ptr<PointerController> PointerController::create(
const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
const sp<SpriteController>& spriteController) {
+ // using 'new' to access non-public constructor
std::shared_ptr<PointerController> controller = std::shared_ptr<PointerController>(
new PointerController(policy, looper, spriteController));
@@ -60,758 +55,175 @@ std::shared_ptr<PointerController> PointerController::create(
* weak_ptr's which themselves cannot be constructed until there's at least one shared_ptr.
*/
- controller->mHandler->pointerController = controller;
- controller->mCallback->pointerController = controller;
- if (controller->mDisplayEventReceiver.initCheck() == NO_ERROR) {
- controller->mLooper->addFd(controller->mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK,
- Looper::EVENT_INPUT, controller->mCallback, nullptr);
- } else {
- ALOGE("Failed to initialize DisplayEventReceiver.");
- }
+ controller->mContext.setHandlerController(controller);
+ controller->mContext.setCallbackController(controller);
+ controller->mContext.initializeDisplayEventReceiver();
return controller;
}
PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
const sp<Looper>& looper,
const sp<SpriteController>& spriteController)
- : mPolicy(policy),
- mLooper(looper),
- mSpriteController(spriteController),
- mHandler(new MessageHandler()),
- mCallback(new LooperCallback()) {
- AutoMutex _l(mLock);
-
- mLocked.animationPending = false;
-
- mLocked.presentation = Presentation::POINTER;
- mLocked.presentationChanged = false;
-
- mLocked.inactivityTimeout = InactivityTimeout::NORMAL;
-
- mLocked.pointerFadeDirection = 0;
- mLocked.pointerX = 0;
- mLocked.pointerY = 0;
- mLocked.pointerAlpha = 0.0f; // pointer is initially faded
- mLocked.pointerSprite = mSpriteController->createSprite();
- mLocked.pointerIconChanged = false;
- mLocked.requestedPointerType = mPolicy->getDefaultPointerIconId();
-
- mLocked.animationFrameIndex = 0;
- mLocked.lastFrameUpdatedTime = 0;
-
- mLocked.buttonState = 0;
+ : mContext(policy, looper, spriteController, *this), mCursorController(mContext) {
+ std::scoped_lock lock(mLock);
+ mLocked.presentation = Presentation::SPOT;
}
-PointerController::~PointerController() {
- mLooper->removeMessages(mHandler);
-
- AutoMutex _l(mLock);
-
- mLocked.pointerSprite.clear();
-
- for (auto& it : mLocked.spotsByDisplay) {
- const std::vector<Spot*>& spots = it.second;
- size_t numSpots = spots.size();
- for (size_t i = 0; i < numSpots; i++) {
- delete spots[i];
- }
- }
- mLocked.spotsByDisplay.clear();
- mLocked.recycledSprites.clear();
-}
-
-bool PointerController::getBounds(float* outMinX, float* outMinY,
- float* outMaxX, float* outMaxY) const {
- AutoMutex _l(mLock);
-
- return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
-}
-
-bool PointerController::getBoundsLocked(float* outMinX, float* outMinY,
- float* outMaxX, float* outMaxY) const {
-
- if (!mLocked.viewport.isValid()) {
- return false;
- }
-
- *outMinX = mLocked.viewport.logicalLeft;
- *outMinY = mLocked.viewport.logicalTop;
- *outMaxX = mLocked.viewport.logicalRight - 1;
- *outMaxY = mLocked.viewport.logicalBottom - 1;
- return true;
+bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
+ float* outMaxY) const {
+ return mCursorController.getBounds(outMinX, outMinY, outMaxX, outMaxY);
}
void PointerController::move(float deltaX, float deltaY) {
-#if DEBUG_POINTER_UPDATES
- ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
-#endif
- if (deltaX == 0.0f && deltaY == 0.0f) {
- return;
- }
-
- AutoMutex _l(mLock);
-
- setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
+ mCursorController.move(deltaX, deltaY);
}
void PointerController::setButtonState(int32_t buttonState) {
-#if DEBUG_POINTER_UPDATES
- ALOGD("Set button state 0x%08x", buttonState);
-#endif
- AutoMutex _l(mLock);
-
- if (mLocked.buttonState != buttonState) {
- mLocked.buttonState = buttonState;
- }
+ mCursorController.setButtonState(buttonState);
}
int32_t PointerController::getButtonState() const {
- AutoMutex _l(mLock);
-
- return mLocked.buttonState;
+ return mCursorController.getButtonState();
}
void PointerController::setPosition(float x, float y) {
-#if DEBUG_POINTER_UPDATES
- ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
-#endif
- AutoMutex _l(mLock);
-
- setPositionLocked(x, y);
-}
-
-void PointerController::setPositionLocked(float x, float y) {
- float minX, minY, maxX, maxY;
- if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
- if (x <= minX) {
- mLocked.pointerX = minX;
- } else if (x >= maxX) {
- mLocked.pointerX = maxX;
- } else {
- mLocked.pointerX = x;
- }
- if (y <= minY) {
- mLocked.pointerY = minY;
- } else if (y >= maxY) {
- mLocked.pointerY = maxY;
- } else {
- mLocked.pointerY = y;
- }
- updatePointerLocked();
- }
+ std::scoped_lock lock(mLock);
+ mCursorController.setPosition(x, y);
}
void PointerController::getPosition(float* outX, float* outY) const {
- AutoMutex _l(mLock);
-
- *outX = mLocked.pointerX;
- *outY = mLocked.pointerY;
+ mCursorController.getPosition(outX, outY);
}
int32_t PointerController::getDisplayId() const {
- AutoMutex _l(mLock);
-
- return mLocked.viewport.displayId;
+ return mCursorController.getDisplayId();
}
void PointerController::fade(Transition transition) {
- AutoMutex _l(mLock);
-
- // Remove the inactivity timeout, since we are fading now.
- removeInactivityTimeoutLocked();
-
- // Start fading.
- if (transition == Transition::IMMEDIATE) {
- mLocked.pointerFadeDirection = 0;
- mLocked.pointerAlpha = 0.0f;
- updatePointerLocked();
- } else {
- mLocked.pointerFadeDirection = -1;
- startAnimationLocked();
- }
+ std::scoped_lock lock(mLock);
+ mCursorController.fade(transition);
}
void PointerController::unfade(Transition transition) {
- AutoMutex _l(mLock);
-
- // Always reset the inactivity timer.
- resetInactivityTimeoutLocked();
-
- // Start unfading.
- if (transition == Transition::IMMEDIATE) {
- mLocked.pointerFadeDirection = 0;
- mLocked.pointerAlpha = 1.0f;
- updatePointerLocked();
- } else {
- mLocked.pointerFadeDirection = 1;
- startAnimationLocked();
- }
+ std::scoped_lock lock(mLock);
+ mCursorController.unfade(transition);
}
void PointerController::setPresentation(Presentation presentation) {
- AutoMutex _l(mLock);
+ std::scoped_lock lock(mLock);
if (mLocked.presentation == presentation) {
return;
}
mLocked.presentation = presentation;
- mLocked.presentationChanged = true;
- if (!mLocked.viewport.isValid()) {
+ if (!mCursorController.isViewportValid()) {
return;
}
if (presentation == Presentation::POINTER) {
- if (mLocked.additionalMouseResources.empty()) {
- mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
- &mLocked.animationResources,
- mLocked.viewport.displayId);
- }
- fadeOutAndReleaseAllSpotsLocked();
- updatePointerLocked();
+ mCursorController.getAdditionalMouseResources();
+ clearSpotsLocked();
}
}
-void PointerController::setSpots(const PointerCoords* spotCoords,
- const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId) {
-#if DEBUG_POINTER_UPDATES
- ALOGD("setSpots: idBits=%08x", spotIdBits.value);
- for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
- uint32_t id = idBits.firstMarkedBit();
- idBits.clearBit(id);
- const PointerCoords& c = spotCoords[spotIdToIndex[id]];
- ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id,
- c.getAxisValue(AMOTION_EVENT_AXIS_X),
- c.getAxisValue(AMOTION_EVENT_AXIS_Y),
- c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
- displayId);
+void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits, int32_t displayId) {
+ std::scoped_lock lock(mLock);
+ auto it = mLocked.spotControllers.find(displayId);
+ if (it == mLocked.spotControllers.end()) {
+ mLocked.spotControllers.try_emplace(displayId, displayId, mContext);
}
-#endif
-
- AutoMutex _l(mLock);
- if (!mLocked.viewport.isValid()) {
- return;
- }
-
- std::vector<Spot*> newSpots;
- std::map<int32_t, std::vector<Spot*>>::const_iterator iter =
- mLocked.spotsByDisplay.find(displayId);
- if (iter != mLocked.spotsByDisplay.end()) {
- newSpots = iter->second;
- }
-
- mSpriteController->openTransaction();
-
- // Add or move spots for fingers that are down.
- for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
- uint32_t id = idBits.clearFirstMarkedBit();
- const PointerCoords& c = spotCoords[spotIdToIndex[id]];
- const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0
- ? mResources.spotTouch : mResources.spotHover;
- float x = c.getAxisValue(AMOTION_EVENT_AXIS_X);
- float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
-
- Spot* spot = getSpot(id, newSpots);
- if (!spot) {
- spot = createAndAddSpotLocked(id, newSpots);
- }
-
- spot->updateSprite(&icon, x, y, displayId);
- }
-
- // Remove spots for fingers that went up.
- for (size_t i = 0; i < newSpots.size(); i++) {
- Spot* spot = newSpots[i];
- if (spot->id != Spot::INVALID_ID
- && !spotIdBits.hasBit(spot->id)) {
- fadeOutAndReleaseSpotLocked(spot);
- }
- }
-
- mSpriteController->closeTransaction();
- mLocked.spotsByDisplay[displayId] = newSpots;
+ mLocked.spotControllers.at(displayId).setSpots(spotCoords, spotIdToIndex, spotIdBits);
}
void PointerController::clearSpots() {
-#if DEBUG_POINTER_UPDATES
- ALOGD("clearSpots");
-#endif
+ std::scoped_lock lock(mLock);
+ clearSpotsLocked();
+}
- AutoMutex _l(mLock);
- if (!mLocked.viewport.isValid()) {
- return;
+void PointerController::clearSpotsLocked() REQUIRES(mLock) {
+ for (auto& [displayID, spotController] : mLocked.spotControllers) {
+ spotController.clearSpots();
}
-
- fadeOutAndReleaseAllSpotsLocked();
}
void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout) {
- AutoMutex _l(mLock);
-
- if (mLocked.inactivityTimeout != inactivityTimeout) {
- mLocked.inactivityTimeout = inactivityTimeout;
- resetInactivityTimeoutLocked();
- }
+ mContext.setInactivityTimeout(inactivityTimeout);
}
void PointerController::reloadPointerResources() {
- AutoMutex _l(mLock);
+ std::scoped_lock lock(mLock);
- loadResourcesLocked();
- updatePointerLocked();
-}
-
-/**
- * The viewport values for deviceHeight and deviceWidth have already been adjusted for rotation,
- * so here we are getting the dimensions in the original, unrotated orientation (orientation 0).
- */
-static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, int32_t& height) {
- width = viewport.deviceWidth;
- height = viewport.deviceHeight;
+ for (auto& [displayID, spotController] : mLocked.spotControllers) {
+ spotController.reloadSpotResources();
+ }
- if (viewport.orientation == DISPLAY_ORIENTATION_90
- || viewport.orientation == DISPLAY_ORIENTATION_270) {
- std::swap(width, height);
+ if (mCursorController.resourcesLoaded()) {
+ bool getAdditionalMouseResources = false;
+ if (mLocked.presentation == PointerController::Presentation::POINTER) {
+ getAdditionalMouseResources = true;
+ }
+ mCursorController.reloadPointerResources(getAdditionalMouseResources);
}
}
void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
- AutoMutex _l(mLock);
- if (viewport == mLocked.viewport) {
- return;
- }
-
- const DisplayViewport oldViewport = mLocked.viewport;
- mLocked.viewport = viewport;
-
- int32_t oldDisplayWidth, oldDisplayHeight;
- getNonRotatedSize(oldViewport, oldDisplayWidth, oldDisplayHeight);
- int32_t newDisplayWidth, newDisplayHeight;
- getNonRotatedSize(viewport, newDisplayWidth, newDisplayHeight);
-
- // Reset cursor position to center if size or display changed.
- if (oldViewport.displayId != viewport.displayId
- || oldDisplayWidth != newDisplayWidth
- || oldDisplayHeight != newDisplayHeight) {
-
- float minX, minY, maxX, maxY;
- if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
- mLocked.pointerX = (minX + maxX) * 0.5f;
- mLocked.pointerY = (minY + maxY) * 0.5f;
- // Reload icon resources for density may be changed.
- loadResourcesLocked();
- } else {
- mLocked.pointerX = 0;
- mLocked.pointerY = 0;
- }
+ std::scoped_lock lock(mLock);
- fadeOutAndReleaseAllSpotsLocked();
- } else if (oldViewport.orientation != viewport.orientation) {
- // Apply offsets to convert from the pixel top-left corner position to the pixel center.
- // This creates an invariant frame of reference that we can easily rotate when
- // taking into account that the pointer may be located at fractional pixel offsets.
- float x = mLocked.pointerX + 0.5f;
- float y = mLocked.pointerY + 0.5f;
- float temp;
-
- // Undo the previous rotation.
- switch (oldViewport.orientation) {
- case DISPLAY_ORIENTATION_90:
- temp = x;
- x = oldViewport.deviceHeight - y;
- y = temp;
- break;
- case DISPLAY_ORIENTATION_180:
- x = oldViewport.deviceWidth - x;
- y = oldViewport.deviceHeight - y;
- break;
- case DISPLAY_ORIENTATION_270:
- temp = x;
- x = y;
- y = oldViewport.deviceWidth - temp;
- break;
- }
-
- // Perform the new rotation.
- switch (viewport.orientation) {
- case DISPLAY_ORIENTATION_90:
- temp = x;
- x = y;
- y = viewport.deviceHeight - temp;
- break;
- case DISPLAY_ORIENTATION_180:
- x = viewport.deviceWidth - x;
- y = viewport.deviceHeight - y;
- break;
- case DISPLAY_ORIENTATION_270:
- temp = x;
- x = viewport.deviceWidth - y;
- y = temp;
- break;
- }
-
- // Apply offsets to convert from the pixel center to the pixel top-left corner position
- // and save the results.
- mLocked.pointerX = x - 0.5f;
- mLocked.pointerY = y - 0.5f;
+ bool getAdditionalMouseResources = false;
+ if (mLocked.presentation == PointerController::Presentation::POINTER) {
+ getAdditionalMouseResources = true;
}
-
- updatePointerLocked();
+ mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
}
void PointerController::updatePointerIcon(int32_t iconId) {
- AutoMutex _l(mLock);
- if (mLocked.requestedPointerType != iconId) {
- mLocked.requestedPointerType = iconId;
- mLocked.presentationChanged = true;
- updatePointerLocked();
- }
+ std::scoped_lock lock(mLock);
+ mCursorController.updatePointerIcon(iconId);
}
void PointerController::setCustomPointerIcon(const SpriteIcon& icon) {
- AutoMutex _l(mLock);
-
- const int32_t iconId = mPolicy->getCustomPointerIconId();
- mLocked.additionalMouseResources[iconId] = icon;
- mLocked.requestedPointerType = iconId;
- mLocked.presentationChanged = true;
-
- updatePointerLocked();
-}
-
-void PointerController::MessageHandler::handleMessage(const Message& message) {
- std::shared_ptr<PointerController> controller = pointerController.lock();
-
- if (controller == nullptr) {
- ALOGE("PointerController instance was released before processing message: what=%d",
- message.what);
- return;
- }
- switch (message.what) {
- case MSG_INACTIVITY_TIMEOUT:
- controller->doInactivityTimeout();
- break;
- }
-}
-
-int PointerController::LooperCallback::handleEvent(int /* fd */, int events, void* /* data */) {
- std::shared_ptr<PointerController> controller = pointerController.lock();
- if (controller == nullptr) {
- ALOGW("PointerController instance was released with pending callbacks. events=0x%x",
- events);
- return 0; // Remove the callback, the PointerController is gone anyways
- }
- if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
- ALOGE("Display event receiver pipe was closed or an error occurred. events=0x%x", events);
- return 0; // remove the callback
- }
-
- if (!(events & Looper::EVENT_INPUT)) {
- ALOGW("Received spurious callback for unhandled poll event. events=0x%x", events);
- return 1; // keep the callback
- }
-
- bool gotVsync = false;
- ssize_t n;
- nsecs_t timestamp;
- DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
- while ((n = controller->mDisplayEventReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
- for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
- if (buf[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
- timestamp = buf[i].header.timestamp;
- gotVsync = true;
- }
- }
- }
- if (gotVsync) {
- controller->doAnimate(timestamp);
- }
- return 1; // keep the callback
+ std::scoped_lock lock(mLock);
+ mCursorController.setCustomPointerIcon(icon);
}
void PointerController::doAnimate(nsecs_t timestamp) {
- AutoMutex _l(mLock);
-
- mLocked.animationPending = false;
+ std::scoped_lock lock(mLock);
- bool keepFading = doFadingAnimationLocked(timestamp);
- bool keepBitmapFlipping = doBitmapAnimationLocked(timestamp);
- if (keepFading || keepBitmapFlipping) {
- startAnimationLocked();
- }
-}
+ mContext.setAnimationPending(false);
-bool PointerController::doFadingAnimationLocked(nsecs_t timestamp) {
- bool keepAnimating = false;
- nsecs_t frameDelay = timestamp - mLocked.animationTime;
+ bool keepFading = false;
+ keepFading = mCursorController.doFadingAnimation(timestamp, keepFading);
- // Animate pointer fade.
- if (mLocked.pointerFadeDirection < 0) {
- mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION;
- if (mLocked.pointerAlpha <= 0.0f) {
- mLocked.pointerAlpha = 0.0f;
- mLocked.pointerFadeDirection = 0;
- } else {
- keepAnimating = true;
- }
- updatePointerLocked();
- } else if (mLocked.pointerFadeDirection > 0) {
- mLocked.pointerAlpha += float(frameDelay) / POINTER_FADE_DURATION;
- if (mLocked.pointerAlpha >= 1.0f) {
- mLocked.pointerAlpha = 1.0f;
- mLocked.pointerFadeDirection = 0;
- } else {
- keepAnimating = true;
- }
- updatePointerLocked();
+ for (auto& [displayID, spotController] : mLocked.spotControllers) {
+ keepFading = spotController.doFadingAnimation(timestamp, keepFading);
}
- // Animate spots that are fading out and being removed.
- for(auto it = mLocked.spotsByDisplay.begin(); it != mLocked.spotsByDisplay.end();) {
- std::vector<Spot*>& spots = it->second;
- size_t numSpots = spots.size();
- for (size_t i = 0; i < numSpots;) {
- Spot* spot = spots[i];
- if (spot->id == Spot::INVALID_ID) {
- spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION;
- if (spot->alpha <= 0) {
- spots.erase(spots.begin() + i);
- releaseSpotLocked(spot);
- numSpots--;
- continue;
- } else {
- spot->sprite->setAlpha(spot->alpha);
- keepAnimating = true;
- }
- }
- ++i;
- }
-
- if (spots.size() == 0) {
- it = mLocked.spotsByDisplay.erase(it);
- } else {
- ++it;
- }
- }
-
- return keepAnimating;
-}
-
-bool PointerController::doBitmapAnimationLocked(nsecs_t timestamp) {
- std::map<int32_t, PointerAnimation>::const_iterator iter = mLocked.animationResources.find(
- mLocked.requestedPointerType);
- if (iter == mLocked.animationResources.end()) {
- return false;
- }
-
- if (timestamp - mLocked.lastFrameUpdatedTime > iter->second.durationPerFrame) {
- mSpriteController->openTransaction();
-
- int incr = (timestamp - mLocked.lastFrameUpdatedTime) / iter->second.durationPerFrame;
- mLocked.animationFrameIndex += incr;
- mLocked.lastFrameUpdatedTime += iter->second.durationPerFrame * incr;
- while (mLocked.animationFrameIndex >= iter->second.animationFrames.size()) {
- mLocked.animationFrameIndex -= iter->second.animationFrames.size();
- }
- mLocked.pointerSprite->setIcon(iter->second.animationFrames[mLocked.animationFrameIndex]);
-
- mSpriteController->closeTransaction();
+ bool keepBitmapFlipping = mCursorController.doBitmapAnimation(timestamp);
+ if (keepFading || keepBitmapFlipping) {
+ mContext.startAnimation();
}
-
- // Keep animating.
- return true;
}
void PointerController::doInactivityTimeout() {
fade(Transition::GRADUAL);
}
-void PointerController::startAnimationLocked() {
- if (!mLocked.animationPending) {
- mLocked.animationPending = true;
- mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
- mDisplayEventReceiver.requestNextVsync();
- }
-}
-
-void PointerController::resetInactivityTimeoutLocked() {
- mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
-
- nsecs_t timeout = mLocked.inactivityTimeout == InactivityTimeout::SHORT
- ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT
- : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL;
- mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT);
-}
-
-void PointerController::removeInactivityTimeoutLocked() {
- mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
-}
-
-void PointerController::updatePointerLocked() REQUIRES(mLock) {
- if (!mLocked.viewport.isValid()) {
- return;
- }
-
- mSpriteController->openTransaction();
-
- mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
- mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
- mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId);
-
- if (mLocked.pointerAlpha > 0) {
- mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
- mLocked.pointerSprite->setVisible(true);
- } else {
- mLocked.pointerSprite->setVisible(false);
- }
-
- if (mLocked.pointerIconChanged || mLocked.presentationChanged) {
- if (mLocked.presentation == Presentation::POINTER) {
- if (mLocked.requestedPointerType == mPolicy->getDefaultPointerIconId()) {
- mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
- } else {
- std::map<int32_t, SpriteIcon>::const_iterator iter =
- mLocked.additionalMouseResources.find(mLocked.requestedPointerType);
- if (iter != mLocked.additionalMouseResources.end()) {
- std::map<int32_t, PointerAnimation>::const_iterator anim_iter =
- mLocked.animationResources.find(mLocked.requestedPointerType);
- if (anim_iter != mLocked.animationResources.end()) {
- mLocked.animationFrameIndex = 0;
- mLocked.lastFrameUpdatedTime = systemTime(SYSTEM_TIME_MONOTONIC);
- startAnimationLocked();
- }
- mLocked.pointerSprite->setIcon(iter->second);
- } else {
- ALOGW("Can't find the resource for icon id %d", mLocked.requestedPointerType);
- mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
- }
- }
- } else {
- mLocked.pointerSprite->setIcon(mResources.spotAnchor);
- }
- mLocked.pointerIconChanged = false;
- mLocked.presentationChanged = false;
- }
-
- mSpriteController->closeTransaction();
-}
-
-PointerController::Spot* PointerController::getSpot(uint32_t id, const std::vector<Spot*>& spots) {
- for (size_t i = 0; i < spots.size(); i++) {
- Spot* spot = spots[i];
- if (spot->id == id) {
- return spot;
- }
+void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports) {
+ std::unordered_set<int32_t> displayIdSet;
+ for (DisplayViewport viewport : viewports) {
+ displayIdSet.insert(viewport.displayId);
}
- return nullptr;
-}
-
-PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id,
- std::vector<Spot*>& spots) {
- // Remove spots until we have fewer than MAX_SPOTS remaining.
- while (spots.size() >= MAX_SPOTS) {
- Spot* spot = removeFirstFadingSpotLocked(spots);
- if (!spot) {
- spot = spots[0];
- spots.erase(spots.begin());
- }
- releaseSpotLocked(spot);
- }
-
- // Obtain a sprite from the recycled pool.
- sp<Sprite> sprite;
- if (! mLocked.recycledSprites.empty()) {
- sprite = mLocked.recycledSprites.back();
- mLocked.recycledSprites.pop_back();
- } else {
- sprite = mSpriteController->createSprite();
- }
-
- // Return the new spot.
- Spot* spot = new Spot(id, sprite);
- spots.push_back(spot);
- return spot;
-}
-
-PointerController::Spot* PointerController::removeFirstFadingSpotLocked(std::vector<Spot*>& spots) {
- for (size_t i = 0; i < spots.size(); i++) {
- Spot* spot = spots[i];
- if (spot->id == Spot::INVALID_ID) {
- spots.erase(spots.begin() + i);
- return spot;
- }
- }
- return nullptr;
-}
-
-void PointerController::releaseSpotLocked(Spot* spot) {
- spot->sprite->clearIcon();
-
- if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) {
- mLocked.recycledSprites.push_back(spot->sprite);
- }
-
- delete spot;
-}
-
-void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) {
- if (spot->id != Spot::INVALID_ID) {
- spot->id = Spot::INVALID_ID;
- startAnimationLocked();
- }
-}
-
-void PointerController::fadeOutAndReleaseAllSpotsLocked() {
- for (auto& it : mLocked.spotsByDisplay) {
- const std::vector<Spot*>& spots = it.second;
- size_t numSpots = spots.size();
- for (size_t i = 0; i < numSpots; i++) {
- Spot* spot = spots[i];
- fadeOutAndReleaseSpotLocked(spot);
- }
- }
-}
-
-void PointerController::loadResourcesLocked() REQUIRES(mLock) {
- if (!mLocked.viewport.isValid()) {
- return;
- }
-
- mPolicy->loadPointerResources(&mResources, mLocked.viewport.displayId);
- mPolicy->loadPointerIcon(&mLocked.pointerIcon, mLocked.viewport.displayId);
-
- mLocked.additionalMouseResources.clear();
- mLocked.animationResources.clear();
- if (mLocked.presentation == Presentation::POINTER) {
- mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
- &mLocked.animationResources, mLocked.viewport.displayId);
- }
-
- mLocked.pointerIconChanged = true;
-}
-
-
-// --- PointerController::Spot ---
-
-void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y,
- int32_t displayId) {
- sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
- sprite->setAlpha(alpha);
- sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
- sprite->setPosition(x, y);
- sprite->setDisplayId(displayId);
- this->x = x;
- this->y = y;
-
- if (icon != lastIcon) {
- lastIcon = icon;
- if (icon) {
- sprite->setIcon(*icon);
- sprite->setVisible(true);
+ std::scoped_lock lock(mLock);
+ for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) {
+ int32_t displayID = it->first;
+ if (!displayIdSet.count(displayID)) {
+ it = mLocked.spotControllers.erase(it);
} else {
- sprite->setVisible(false);
+ ++it;
}
}
}
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 14c0679654c6..1f561da333b1 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -30,48 +30,14 @@
#include <memory>
#include <vector>
+#include "MouseCursorController.h"
+#include "PointerControllerContext.h"
#include "SpriteController.h"
+#include "TouchSpotController.h"
namespace android {
/*
- * Pointer resources.
- */
-struct PointerResources {
- SpriteIcon spotHover;
- SpriteIcon spotTouch;
- SpriteIcon spotAnchor;
-};
-
-struct PointerAnimation {
- std::vector<SpriteIcon> animationFrames;
- nsecs_t durationPerFrame;
-};
-
-/*
- * Pointer controller policy interface.
- *
- * The pointer controller policy is used by the pointer controller to interact with
- * the Window Manager and other system components.
- *
- * The actual implementation is partially supported by callbacks into the DVM
- * via JNI. This interface is also mocked in the unit tests.
- */
-class PointerControllerPolicyInterface : public virtual RefBase {
-protected:
- PointerControllerPolicyInterface() { }
- virtual ~PointerControllerPolicyInterface() { }
-
-public:
- virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) = 0;
- virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) = 0;
- virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources,
- std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) = 0;
- virtual int32_t getDefaultPointerIconId() = 0;
- virtual int32_t getCustomPointerIconId() = 0;
-};
-
-/*
* Tracks pointer movements and draws the pointer sprite to a surface.
*
* Handles pointer acceleration and animation.
@@ -81,15 +47,10 @@ public:
static std::shared_ptr<PointerController> create(
const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
const sp<SpriteController>& spriteController);
- enum class InactivityTimeout {
- NORMAL = 0,
- SHORT = 1,
- };
- virtual ~PointerController();
+ virtual ~PointerController() = default;
- virtual bool getBounds(float* outMinX, float* outMinY,
- float* outMaxX, float* outMaxY) const;
+ virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
virtual void move(float deltaX, float deltaY);
virtual void setButtonState(int32_t buttonState);
virtual int32_t getButtonState() const;
@@ -101,129 +62,37 @@ public:
virtual void setDisplayViewport(const DisplayViewport& viewport);
virtual void setPresentation(Presentation presentation);
- virtual void setSpots(const PointerCoords* spotCoords,
- const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId);
+ virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits, int32_t displayId);
virtual void clearSpots();
void updatePointerIcon(int32_t iconId);
void setCustomPointerIcon(const SpriteIcon& icon);
void setInactivityTimeout(InactivityTimeout inactivityTimeout);
+ void doInactivityTimeout();
+ void doAnimate(nsecs_t timestamp);
void reloadPointerResources();
+ void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports);
private:
- static constexpr size_t MAX_RECYCLED_SPRITES = 12;
- static constexpr size_t MAX_SPOTS = 12;
-
- enum {
- MSG_INACTIVITY_TIMEOUT,
- };
-
- struct Spot {
- static const uint32_t INVALID_ID = 0xffffffff;
-
- uint32_t id;
- sp<Sprite> sprite;
- float alpha;
- float scale;
- float x, y;
-
- inline Spot(uint32_t id, const sp<Sprite>& sprite)
- : id(id),
- sprite(sprite),
- alpha(1.0f),
- scale(1.0f),
- x(0.0f),
- y(0.0f),
- lastIcon(nullptr) {}
-
- void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId);
+ friend PointerControllerContext::LooperCallback;
+ friend PointerControllerContext::MessageHandler;
- private:
- const SpriteIcon* lastIcon;
- };
+ mutable std::mutex mLock;
- class MessageHandler : public virtual android::MessageHandler {
- public:
- void handleMessage(const Message& message) override;
- std::weak_ptr<PointerController> pointerController;
- };
+ PointerControllerContext mContext;
- class LooperCallback : public virtual android::LooperCallback {
- public:
- int handleEvent(int fd, int events, void* data) override;
- std::weak_ptr<PointerController> pointerController;
- };
-
- mutable Mutex mLock;
-
- sp<PointerControllerPolicyInterface> mPolicy;
- sp<Looper> mLooper;
- sp<SpriteController> mSpriteController;
- sp<MessageHandler> mHandler;
- sp<LooperCallback> mCallback;
-
- DisplayEventReceiver mDisplayEventReceiver;
-
- PointerResources mResources;
+ MouseCursorController mCursorController;
struct Locked {
- bool animationPending;
- nsecs_t animationTime;
-
- size_t animationFrameIndex;
- nsecs_t lastFrameUpdatedTime;
-
- DisplayViewport viewport;
-
- InactivityTimeout inactivityTimeout;
-
Presentation presentation;
- bool presentationChanged;
-
- int32_t pointerFadeDirection;
- float pointerX;
- float pointerY;
- float pointerAlpha;
- sp<Sprite> pointerSprite;
- SpriteIcon pointerIcon;
- bool pointerIconChanged;
-
- std::map<int32_t, SpriteIcon> additionalMouseResources;
- std::map<int32_t, PointerAnimation> animationResources;
- int32_t requestedPointerType;
-
- int32_t buttonState;
-
- std::map<int32_t /* displayId */, std::vector<Spot*>> spotsByDisplay;
- std::vector<sp<Sprite>> recycledSprites;
+ std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
} mLocked GUARDED_BY(mLock);
PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
const sp<SpriteController>& spriteController);
-
- bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
- void setPositionLocked(float x, float y);
-
- void doAnimate(nsecs_t timestamp);
- bool doFadingAnimationLocked(nsecs_t timestamp);
- bool doBitmapAnimationLocked(nsecs_t timestamp);
- void doInactivityTimeout();
-
- void startAnimationLocked();
-
- void resetInactivityTimeoutLocked();
- void removeInactivityTimeoutLocked();
- void updatePointerLocked();
-
- Spot* getSpot(uint32_t id, const std::vector<Spot*>& spots);
- Spot* createAndAddSpotLocked(uint32_t id, std::vector<Spot*>& spots);
- Spot* removeFirstFadingSpotLocked(std::vector<Spot*>& spots);
- void releaseSpotLocked(Spot* spot);
- void fadeOutAndReleaseSpotLocked(Spot* spot);
- void fadeOutAndReleaseAllSpotsLocked();
-
- void loadResourcesLocked();
+ void clearSpotsLocked();
};
} // namespace android
diff --git a/libs/input/PointerControllerContext.cpp b/libs/input/PointerControllerContext.cpp
new file mode 100644
index 000000000000..2d7e22b01112
--- /dev/null
+++ b/libs/input/PointerControllerContext.cpp
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PointerControllerContext.h"
+#include "PointerController.h"
+
+namespace {
+// Time to wait before starting the fade when the pointer is inactive.
+const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
+const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
+
+// The number of events to be read at once for DisplayEventReceiver.
+const int EVENT_BUFFER_SIZE = 100;
+} // namespace
+
+namespace android {
+
+// --- PointerControllerContext ---
+
+PointerControllerContext::PointerControllerContext(
+ const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
+ const sp<SpriteController>& spriteController, PointerController& controller)
+ : mPolicy(policy),
+ mLooper(looper),
+ mSpriteController(spriteController),
+ mHandler(new MessageHandler()),
+ mCallback(new LooperCallback()),
+ mController(controller) {
+ std::scoped_lock lock(mLock);
+ mLocked.inactivityTimeout = InactivityTimeout::NORMAL;
+ mLocked.animationPending = false;
+}
+
+PointerControllerContext::~PointerControllerContext() {
+ mLooper->removeMessages(mHandler);
+}
+
+void PointerControllerContext::setInactivityTimeout(InactivityTimeout inactivityTimeout) {
+ std::scoped_lock lock(mLock);
+
+ if (mLocked.inactivityTimeout != inactivityTimeout) {
+ mLocked.inactivityTimeout = inactivityTimeout;
+ resetInactivityTimeoutLocked();
+ }
+}
+
+void PointerControllerContext::startAnimation() {
+ std::scoped_lock lock(mLock);
+ if (!mLocked.animationPending) {
+ mLocked.animationPending = true;
+ mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mDisplayEventReceiver.requestNextVsync();
+ }
+}
+
+void PointerControllerContext::resetInactivityTimeout() {
+ std::scoped_lock lock(mLock);
+ resetInactivityTimeoutLocked();
+}
+
+void PointerControllerContext::resetInactivityTimeoutLocked() REQUIRES(mLock) {
+ mLooper->removeMessages(mHandler, MessageHandler::MSG_INACTIVITY_TIMEOUT);
+
+ nsecs_t timeout = mLocked.inactivityTimeout == InactivityTimeout::SHORT
+ ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT
+ : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL;
+ mLooper->sendMessageDelayed(timeout, mHandler, MessageHandler::MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerControllerContext::removeInactivityTimeout() {
+ std::scoped_lock lock(mLock);
+ mLooper->removeMessages(mHandler, MessageHandler::MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerControllerContext::setAnimationPending(bool animationPending) {
+ std::scoped_lock lock(mLock);
+ mLocked.animationPending = animationPending;
+}
+
+nsecs_t PointerControllerContext::getAnimationTime() {
+ std::scoped_lock lock(mLock);
+ return mLocked.animationTime;
+}
+
+void PointerControllerContext::setHandlerController(std::shared_ptr<PointerController> controller) {
+ mHandler->pointerController = controller;
+}
+
+void PointerControllerContext::setCallbackController(
+ std::shared_ptr<PointerController> controller) {
+ mCallback->pointerController = controller;
+}
+
+sp<PointerControllerPolicyInterface> PointerControllerContext::getPolicy() {
+ return mPolicy;
+}
+
+sp<SpriteController> PointerControllerContext::getSpriteController() {
+ return mSpriteController;
+}
+
+void PointerControllerContext::initializeDisplayEventReceiver() {
+ if (mDisplayEventReceiver.initCheck() == NO_ERROR) {
+ mLooper->addFd(mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK, Looper::EVENT_INPUT,
+ mCallback, nullptr);
+ } else {
+ ALOGE("Failed to initialize DisplayEventReceiver.");
+ }
+}
+
+void PointerControllerContext::handleDisplayEvents() {
+ bool gotVsync = false;
+ ssize_t n;
+ nsecs_t timestamp;
+ DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
+ while ((n = mDisplayEventReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
+ for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
+ if (buf[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
+ timestamp = buf[i].header.timestamp;
+ gotVsync = true;
+ }
+ }
+ }
+ if (gotVsync) {
+ mController.doAnimate(timestamp);
+ }
+}
+
+void PointerControllerContext::MessageHandler::handleMessage(const Message& message) {
+ std::shared_ptr<PointerController> controller = pointerController.lock();
+
+ if (controller == nullptr) {
+ ALOGE("PointerController instance was released before processing message: what=%d",
+ message.what);
+ return;
+ }
+ switch (message.what) {
+ case MSG_INACTIVITY_TIMEOUT:
+ controller->doInactivityTimeout();
+ break;
+ }
+}
+
+int PointerControllerContext::LooperCallback::handleEvent(int /* fd */, int events,
+ void* /* data */) {
+ std::shared_ptr<PointerController> controller = pointerController.lock();
+ if (controller == nullptr) {
+ ALOGW("PointerController instance was released with pending callbacks. events=0x%x",
+ events);
+ return 0; // Remove the callback, the PointerController is gone anyways
+ }
+ if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
+ ALOGE("Display event receiver pipe was closed or an error occurred. events=0x%x", events);
+ return 0; // remove the callback
+ }
+
+ if (!(events & Looper::EVENT_INPUT)) {
+ ALOGW("Received spurious callback for unhandled poll event. events=0x%x", events);
+ return 1; // keep the callback
+ }
+
+ controller->mContext.handleDisplayEvents();
+ return 1; // keep the callback
+}
+
+} // namespace android
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
new file mode 100644
index 000000000000..92e1bda25f56
--- /dev/null
+++ b/libs/input/PointerControllerContext.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_POINTER_CONTROLLER_CONTEXT_H
+#define _UI_POINTER_CONTROLLER_CONTEXT_H
+
+#include <PointerControllerInterface.h>
+#include <gui/DisplayEventReceiver.h>
+#include <input/DisplayViewport.h>
+#include <input/Input.h>
+#include <ui/DisplayInfo.h>
+#include <utils/BitSet.h>
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "SpriteController.h"
+
+namespace android {
+
+class PointerController;
+
+/*
+ * Pointer resources.
+ */
+struct PointerResources {
+ SpriteIcon spotHover;
+ SpriteIcon spotTouch;
+ SpriteIcon spotAnchor;
+};
+
+struct PointerAnimation {
+ std::vector<SpriteIcon> animationFrames;
+ nsecs_t durationPerFrame;
+};
+
+enum class InactivityTimeout {
+ NORMAL = 0,
+ SHORT = 1,
+};
+
+/*
+ * Pointer controller policy interface.
+ *
+ * The pointer controller policy is used by the pointer controller to interact with
+ * the Window Manager and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI. This interface is also mocked in the unit tests.
+ */
+class PointerControllerPolicyInterface : public virtual RefBase {
+protected:
+ PointerControllerPolicyInterface() {}
+ virtual ~PointerControllerPolicyInterface() {}
+
+public:
+ virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) = 0;
+ virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) = 0;
+ virtual void loadAdditionalMouseResources(
+ std::map<int32_t, SpriteIcon>* outResources,
+ std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) = 0;
+ virtual int32_t getDefaultPointerIconId() = 0;
+ virtual int32_t getCustomPointerIconId() = 0;
+};
+
+/*
+ * Contains logic and resources shared among PointerController,
+ * MouseCursorController, and TouchSpotController.
+ */
+
+class PointerControllerContext {
+public:
+ PointerControllerContext(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper, const sp<SpriteController>& spriteController,
+ PointerController& controller);
+ ~PointerControllerContext();
+
+ void removeInactivityTimeout();
+ void resetInactivityTimeout();
+ void startAnimation();
+ void setInactivityTimeout(InactivityTimeout inactivityTimeout);
+
+ void setAnimationPending(bool animationPending);
+ nsecs_t getAnimationTime();
+
+ void clearSpotsByDisplay(int32_t displayId);
+
+ void setHandlerController(std::shared_ptr<PointerController> controller);
+ void setCallbackController(std::shared_ptr<PointerController> controller);
+
+ sp<PointerControllerPolicyInterface> getPolicy();
+ sp<SpriteController> getSpriteController();
+
+ void initializeDisplayEventReceiver();
+ void handleDisplayEvents();
+
+ class MessageHandler : public virtual android::MessageHandler {
+ public:
+ enum {
+ MSG_INACTIVITY_TIMEOUT,
+ };
+
+ void handleMessage(const Message& message) override;
+ std::weak_ptr<PointerController> pointerController;
+ };
+
+ class LooperCallback : public virtual android::LooperCallback {
+ public:
+ int handleEvent(int fd, int events, void* data) override;
+ std::weak_ptr<PointerController> pointerController;
+ };
+
+private:
+ sp<PointerControllerPolicyInterface> mPolicy;
+ sp<Looper> mLooper;
+ sp<SpriteController> mSpriteController;
+ sp<MessageHandler> mHandler;
+ sp<LooperCallback> mCallback;
+
+ DisplayEventReceiver mDisplayEventReceiver;
+
+ PointerController& mController;
+
+ mutable std::mutex mLock;
+
+ struct Locked {
+ bool animationPending;
+ nsecs_t animationTime;
+
+ InactivityTimeout inactivityTimeout;
+ } mLocked GUARDED_BY(mLock);
+
+ void resetInactivityTimeoutLocked();
+};
+
+} // namespace android
+
+#endif // _UI_POINTER_CONTROLLER_CONTEXT_H
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
new file mode 100644
index 000000000000..c7430ceead41
--- /dev/null
+++ b/libs/input/TouchSpotController.cpp
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "TouchSpotController"
+
+// Log debug messages about pointer updates
+#define DEBUG_SPOT_UPDATES 0
+
+#include "TouchSpotController.h"
+
+#include <log/log.h>
+
+#include <SkBitmap.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
+
+namespace {
+// Time to spend fading out the spot completely.
+const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
+} // namespace
+
+namespace android {
+
+// --- Spot ---
+
+void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float x, float y,
+ int32_t displayId) {
+ sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
+ sprite->setAlpha(alpha);
+ sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
+ sprite->setPosition(x, y);
+ sprite->setDisplayId(displayId);
+ this->x = x;
+ this->y = y;
+
+ if (icon != mLastIcon) {
+ mLastIcon = icon;
+ if (icon) {
+ sprite->setIcon(*icon);
+ sprite->setVisible(true);
+ } else {
+ sprite->setVisible(false);
+ }
+ }
+}
+
+// --- TouchSpotController ---
+
+TouchSpotController::TouchSpotController(int32_t displayId, PointerControllerContext& context)
+ : mDisplayId(displayId), mContext(context) {
+ mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId);
+}
+
+TouchSpotController::~TouchSpotController() {
+ std::scoped_lock lock(mLock);
+
+ size_t numSpots = mLocked.displaySpots.size();
+ for (size_t i = 0; i < numSpots; i++) {
+ delete mLocked.displaySpots[i];
+ }
+ mLocked.displaySpots.clear();
+}
+
+void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits) {
+#if DEBUG_SPOT_UPDATES
+ ALOGD("setSpots: idBits=%08x", spotIdBits.value);
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
+ uint32_t id = idBits.firstMarkedBit();
+ idBits.clearBit(id);
+ const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+ ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id,
+ c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y),
+ c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), displayId);
+ }
+#endif
+
+ std::scoped_lock lock(mLock);
+ sp<SpriteController> spriteController = mContext.getSpriteController();
+ spriteController->openTransaction();
+
+ // Add or move spots for fingers that are down.
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+ const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0
+ ? mResources.spotTouch
+ : mResources.spotHover;
+ float x = c.getAxisValue(AMOTION_EVENT_AXIS_X);
+ float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
+
+ Spot* spot = getSpot(id, mLocked.displaySpots);
+ if (!spot) {
+ spot = createAndAddSpotLocked(id, mLocked.displaySpots);
+ }
+
+ spot->updateSprite(&icon, x, y, mDisplayId);
+ }
+
+ for (Spot* spot : mLocked.displaySpots) {
+ if (spot->id != Spot::INVALID_ID && !spotIdBits.hasBit(spot->id)) {
+ fadeOutAndReleaseSpotLocked(spot);
+ }
+ }
+
+ spriteController->closeTransaction();
+}
+
+void TouchSpotController::clearSpots() {
+#if DEBUG_SPOT_UPDATES
+ ALOGD("clearSpots");
+#endif
+
+ std::scoped_lock lock(mLock);
+ fadeOutAndReleaseAllSpotsLocked();
+}
+
+TouchSpotController::Spot* TouchSpotController::getSpot(uint32_t id,
+ const std::vector<Spot*>& spots) {
+ for (size_t i = 0; i < spots.size(); i++) {
+ Spot* spot = spots[i];
+ if (spot->id == id) {
+ return spot;
+ }
+ }
+ return nullptr;
+}
+
+TouchSpotController::Spot* TouchSpotController::createAndAddSpotLocked(uint32_t id,
+ std::vector<Spot*>& spots) {
+ // Remove spots until we have fewer than MAX_SPOTS remaining.
+ while (spots.size() >= MAX_SPOTS) {
+ Spot* spot = removeFirstFadingSpotLocked(spots);
+ if (!spot) {
+ spot = spots[0];
+ spots.erase(spots.begin());
+ }
+ releaseSpotLocked(spot);
+ }
+
+ // Obtain a sprite from the recycled pool.
+ sp<Sprite> sprite;
+ if (!mLocked.recycledSprites.empty()) {
+ sprite = mLocked.recycledSprites.back();
+ mLocked.recycledSprites.pop_back();
+ } else {
+ sprite = mContext.getSpriteController()->createSprite();
+ }
+
+ // Return the new spot.
+ Spot* spot = new Spot(id, sprite);
+ spots.push_back(spot);
+ return spot;
+}
+
+TouchSpotController::Spot* TouchSpotController::removeFirstFadingSpotLocked(
+ std::vector<Spot*>& spots) REQUIRES(mLock) {
+ for (size_t i = 0; i < spots.size(); i++) {
+ Spot* spot = spots[i];
+ if (spot->id == Spot::INVALID_ID) {
+ spots.erase(spots.begin() + i);
+ return spot;
+ }
+ }
+ return NULL;
+}
+
+void TouchSpotController::releaseSpotLocked(Spot* spot) REQUIRES(mLock) {
+ spot->sprite->clearIcon();
+
+ if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) {
+ mLocked.recycledSprites.push_back(spot->sprite);
+ }
+
+ delete spot;
+}
+
+void TouchSpotController::fadeOutAndReleaseSpotLocked(Spot* spot) REQUIRES(mLock) {
+ if (spot->id != Spot::INVALID_ID) {
+ spot->id = Spot::INVALID_ID;
+ mContext.startAnimation();
+ }
+}
+
+void TouchSpotController::fadeOutAndReleaseAllSpotsLocked() REQUIRES(mLock) {
+ size_t numSpots = mLocked.displaySpots.size();
+ for (size_t i = 0; i < numSpots; i++) {
+ Spot* spot = mLocked.displaySpots[i];
+ fadeOutAndReleaseSpotLocked(spot);
+ }
+}
+
+void TouchSpotController::reloadSpotResources() {
+ mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId);
+}
+
+bool TouchSpotController::doFadingAnimation(nsecs_t timestamp, bool keepAnimating) {
+ std::scoped_lock lock(mLock);
+ nsecs_t animationTime = mContext.getAnimationTime();
+ nsecs_t frameDelay = timestamp - animationTime;
+ size_t numSpots = mLocked.displaySpots.size();
+ for (size_t i = 0; i < numSpots;) {
+ Spot* spot = mLocked.displaySpots[i];
+ if (spot->id == Spot::INVALID_ID) {
+ spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION;
+ if (spot->alpha <= 0) {
+ mLocked.displaySpots.erase(mLocked.displaySpots.begin() + i);
+ releaseSpotLocked(spot);
+ numSpots--;
+ continue;
+ } else {
+ spot->sprite->setAlpha(spot->alpha);
+ keepAnimating = true;
+ }
+ }
+ ++i;
+ }
+ return keepAnimating;
+}
+
+} // namespace android
diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h
new file mode 100644
index 000000000000..f3b355010bee
--- /dev/null
+++ b/libs/input/TouchSpotController.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_TOUCH_SPOT_CONTROLLER_H
+#define _UI_TOUCH_SPOT_CONTROLLER_H
+
+#include "PointerControllerContext.h"
+
+namespace android {
+
+/*
+ * Helper class for PointerController that specifically handles
+ * touch spot resources and actions for a single display.
+ */
+class TouchSpotController {
+public:
+ TouchSpotController(int32_t displayId, PointerControllerContext& context);
+ ~TouchSpotController();
+ void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits);
+ void clearSpots();
+
+ void reloadSpotResources();
+ bool doFadingAnimation(nsecs_t timestamp, bool keepAnimating);
+
+private:
+ struct Spot {
+ static const uint32_t INVALID_ID = 0xffffffff;
+
+ uint32_t id;
+ sp<Sprite> sprite;
+ float alpha;
+ float scale;
+ float x, y;
+
+ inline Spot(uint32_t id, const sp<Sprite>& sprite)
+ : id(id),
+ sprite(sprite),
+ alpha(1.0f),
+ scale(1.0f),
+ x(0.0f),
+ y(0.0f),
+ mLastIcon(nullptr) {}
+
+ void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId);
+
+ private:
+ const SpriteIcon* mLastIcon;
+ };
+
+ int32_t mDisplayId;
+
+ mutable std::mutex mLock;
+
+ PointerResources mResources;
+
+ PointerControllerContext& mContext;
+
+ static constexpr size_t MAX_RECYCLED_SPRITES = 12;
+ static constexpr size_t MAX_SPOTS = 12;
+
+ struct Locked {
+ std::vector<Spot*> displaySpots;
+ std::vector<sp<Sprite>> recycledSprites;
+
+ } mLocked GUARDED_BY(mLock);
+
+ Spot* getSpot(uint32_t id, const std::vector<Spot*>& spots);
+ Spot* createAndAddSpotLocked(uint32_t id, std::vector<Spot*>& spots);
+ Spot* removeFirstFadingSpotLocked(std::vector<Spot*>& spots);
+ void releaseSpotLocked(Spot* spot);
+ void fadeOutAndReleaseSpotLocked(Spot* spot);
+ void fadeOutAndReleaseAllSpotsLocked();
+};
+
+} // namespace android
+
+#endif // _UI_TOUCH_SPOT_CONTROLLER_H
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 6e129a064385..b67088a389b6 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -178,9 +178,6 @@ void PointerControllerTest::ensureDisplayViewportIsSet() {
viewport.deviceWidth = 400;
viewport.deviceHeight = 300;
mPointerController->setDisplayViewport(viewport);
-
- // The first call to setDisplayViewport should trigger the loading of the necessary resources.
- EXPECT_TRUE(mPolicy->allResourcesAreLoaded());
}
void PointerControllerTest::loopThread() {
@@ -208,6 +205,7 @@ TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) {
TEST_F(PointerControllerTest, updatePointerIcon) {
ensureDisplayViewportIsSet();
+ mPointerController->setPresentation(PointerController::Presentation::POINTER);
mPointerController->unfade(PointerController::Transition::IMMEDIATE);
int32_t type = CURSOR_TYPE_ADDITIONAL;
@@ -247,8 +245,6 @@ TEST_F(PointerControllerTest, setCustomPointerIcon) {
TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) {
mPointerController->setPresentation(PointerController::Presentation::POINTER);
- mPointerController->setSpots(nullptr, nullptr, BitSet32(), -1);
- mPointerController->clearSpots();
mPointerController->setPosition(1.0f, 1.0f);
mPointerController->move(1.0f, 1.0f);
mPointerController->unfade(PointerController::Transition::IMMEDIATE);
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index 561a8847feed..697d80c6b78e 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -220,6 +220,34 @@ public class AudioPolicyConfig implements Parcelable {
return textDump;
}
+ /**
+ * Very short dump of configuration
+ * @return a condensed dump of configuration, uniquely identifies a policy in a log
+ */
+ public String toCompactLogString() {
+ String compactDump = "reg:" + mRegistrationId;
+ int mixNum = 0;
+ for (AudioMix mix : mMixes) {
+ compactDump += " Mix:" + mixNum + "-Typ:" + mixTypePrefix(mix.getMixType())
+ + "-Rul:" + mix.getRule().getCriteria().size();
+ mixNum++;
+ }
+ return compactDump;
+ }
+
+ private static String mixTypePrefix(int mixType) {
+ switch (mixType) {
+ case AudioMix.MIX_TYPE_PLAYERS:
+ return "p";
+ case AudioMix.MIX_TYPE_RECORDERS:
+ return "r";
+ case AudioMix.MIX_TYPE_INVALID:
+ default:
+ return "#";
+
+ }
+ }
+
protected void reset() {
mMixCounter = 0;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java
index 4d968f1763ca..73dc60dbc7da 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java
@@ -26,6 +26,17 @@ public class WallpaperEngineCompat {
private static final String TAG = "WallpaperEngineCompat";
+ /**
+ * Returns true if {@link IWallpaperEngine#scalePreview(Rect)} is available.
+ */
+ public static boolean supportsScalePreview() {
+ try {
+ return IWallpaperEngine.class.getMethod("scalePreview", Rect.class) != null;
+ } catch (NoSuchMethodException | SecurityException e) {
+ return false;
+ }
+ }
+
private final IWallpaperEngine mWrappedEngine;
public WallpaperEngineCompat(IWallpaperEngine wrappedEngine) {
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 045c51a516f4..59ac09ca2f3d 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -167,7 +167,7 @@ public class VibratorService extends IVibratorService.Stub
private boolean mIsVibrating;
@GuardedBy("mLock")
private final RemoteCallbackList<IVibratorStateListener> mVibratorStateListeners =
- new RemoteCallbackList<>();
+ new RemoteCallbackList<>();
private int mHapticFeedbackIntensity;
private int mNotificationIntensity;
private int mRingIntensity;
@@ -176,16 +176,25 @@ public class VibratorService extends IVibratorService.Stub
static native long vibratorInit();
static native long vibratorGetFinalizer();
+
static native boolean vibratorExists(long controllerPtr);
- static native void vibratorOn(long milliseconds);
+
+ static native void vibratorOn(long controllerPtr, long milliseconds, Vibration vibration);
+
static native void vibratorOff(long controllerPtr);
+
static native void vibratorSetAmplitude(long controllerPtr, int amplitude);
+
static native int[] vibratorGetSupportedEffects(long controllerPtr);
+
static native long vibratorPerformEffect(long effect, long strength, Vibration vibration,
boolean withCallback);
+
static native void vibratorPerformComposedEffect(long controllerPtr,
VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration);
+
static native void vibratorSetExternalControl(long controllerPtr, boolean enabled);
+
static native long vibratorGetCapabilities(long controllerPtr);
static native void vibratorAlwaysOnEnable(long controllerPtr, long id, long effect,
long strength);
@@ -231,8 +240,7 @@ public class VibratorService extends IVibratorService.Stub
// The actual effect to be played.
public VibrationEffect effect;
- // The original effect that was requested. This is non-null only when the original effect
- // differs from the effect that's being played. Typically these two things differ because
+ // The original effect that was requested. Typically these two things differ because
// the effect was scaled based on the users vibration intensity settings.
public VibrationEffect originalEffect;
@@ -248,6 +256,7 @@ public class VibratorService extends IVibratorService.Stub
this.reason = reason;
}
+ @Override
public void binderDied() {
synchronized (mLock) {
if (this == mCurrentVibration) {
@@ -949,25 +958,22 @@ public class VibratorService extends IVibratorService.Stub
private void startVibrationInnerLocked(Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
try {
+ long timeout = 0;
mCurrentVibration = vib;
if (vib.effect instanceof VibrationEffect.OneShot) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
- doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib.uid, vib.attrs);
- mH.postDelayed(mVibrationEndRunnable, oneShot.getDuration());
+ doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib);
+ timeout = oneShot.getDuration() * ASYNC_TIMEOUT_MULTIPLIER;
} else if (vib.effect instanceof VibrationEffect.Waveform) {
// mThread better be null here. doCancelVibrate should always be
// called before startNextVibrationLocked or startVibrationLocked.
- Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.effect;
mThread = new VibrateThread(waveform, vib.uid, vib.attrs);
mThread.start();
} else if (vib.effect instanceof VibrationEffect.Prebaked) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
- long timeout = doVibratorPrebakedEffectLocked(vib);
- if (timeout > 0) {
- mH.postDelayed(mVibrationEndRunnable, timeout);
- }
+ timeout = doVibratorPrebakedEffectLocked(vib);
} else if (vib.effect instanceof VibrationEffect.Composed) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
doVibratorComposedEffectLocked(vib);
@@ -975,10 +981,16 @@ public class VibratorService extends IVibratorService.Stub
// devices which support composition also support the completion callback. If we
// ever get a device that supports the former but not the latter, then we have no
// real way of knowing how long a given effect should last.
- mH.postDelayed(mVibrationEndRunnable, 10000);
+ timeout = 10_000;
} else {
Slog.e(TAG, "Unknown vibration type, ignoring");
}
+ // Post extra runnable to ensure vibration will end even if the HAL or native controller
+ // never triggers the callback.
+ // TODO: Move ASYNC_TIMEOUT_MULTIPLIER here once native controller is fully integrated.
+ if (timeout > 0) {
+ mH.postDelayed(mVibrationEndRunnable, timeout);
+ }
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
@@ -1271,7 +1283,18 @@ public class VibratorService extends IVibratorService.Stub
return mNativeWrapper.vibratorExists();
}
+ /** Vibrates with native callback trigger for {@link Vibration#onComplete()}. */
+ private void doVibratorOn(long millis, int amplitude, Vibration vib) {
+ doVibratorOn(millis, amplitude, vib.uid, vib.attrs, vib);
+ }
+
+ /** Vibrates without native callback. */
private void doVibratorOn(long millis, int amplitude, int uid, VibrationAttributes attrs) {
+ doVibratorOn(millis, amplitude, uid, attrs, /* vib= */ null);
+ }
+
+ private void doVibratorOn(long millis, int amplitude, int uid, VibrationAttributes attrs,
+ @Nullable Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn");
try {
synchronized (mInputDeviceVibrators) {
@@ -1286,13 +1309,14 @@ public class VibratorService extends IVibratorService.Stub
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
for (int i = 0; i < vibratorCount; i++) {
- mInputDeviceVibrators.get(i).vibrate(millis, attrs.getAudioAttributes());
+ Vibrator inputDeviceVibrator = mInputDeviceVibrators.get(i);
+ inputDeviceVibrator.vibrate(millis, attrs.getAudioAttributes());
}
} else {
// Note: ordering is important here! Many haptic drivers will reset their
// amplitude when enabled, so we always have to enable first, then set the
// amplitude.
- mNativeWrapper.vibratorOn(millis);
+ mNativeWrapper.vibratorOn(millis, vib);
doVibratorSetAmplitude(amplitude);
}
}
@@ -1576,6 +1600,7 @@ public class VibratorService extends IVibratorService.Stub
proto.flush();
}
+ /** Thread that plays a single {@link VibrationEffect.Waveform}. */
private class VibrateThread extends Thread {
private final VibrationEffect.Waveform mWaveform;
private final int mUid;
@@ -1744,8 +1769,8 @@ public class VibratorService extends IVibratorService.Stub
}
/** Turns vibrator on for given time. */
- public void vibratorOn(long milliseconds) {
- VibratorService.vibratorOn(milliseconds);
+ public void vibratorOn(long milliseconds, @Nullable Vibration vibration) {
+ VibratorService.vibratorOn(mNativeControllerPtr, milliseconds, vibration);
}
/** Turns vibrator off. */
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 366f30318cd5..23b09294260c 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7903,9 +7903,6 @@ public class AudioService extends IAudioService.Stub
return null;
}
- mDynPolicyLogger.log((new AudioEventLogger.StringEvent("registerAudioPolicy for "
- + pcb.asBinder() + " with config:" + policyConfig)).printLog(TAG));
-
String regId = null;
synchronized (mAudioPolicies) {
if (mAudioPolicies.containsKey(pcb.asBinder())) {
@@ -7916,6 +7913,13 @@ public class AudioService extends IAudioService.Stub
AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener,
isFocusPolicy, isTestFocusPolicy, isVolumeController, projection);
pcb.asBinder().linkToDeath(app, 0/*flags*/);
+
+ // logging after registration so we have the registration id
+ mDynPolicyLogger.log((new AudioEventLogger.StringEvent("registerAudioPolicy for "
+ + pcb.asBinder() + " u/pid:" + Binder.getCallingUid() + "/"
+ + Binder.getCallingPid() + " with config:" + app.toCompactLogString()))
+ .printLog(TAG));
+
regId = app.getRegistrationId();
mAudioPolicies.put(pcb.asBinder(), app);
} catch (RemoteException e) {
@@ -8079,7 +8083,10 @@ public class AudioService extends IAudioService.Stub
* @param pcb nullable because on service interface
*/
public void unregisterAudioPolicyAsync(@Nullable IAudioPolicyCallback pcb) {
- unregisterAudioPolicy(pcb);
+ if (pcb == null) {
+ return;
+ }
+ unregisterAudioPolicyInt(pcb, "unregisterAudioPolicyAsync");
}
/**
@@ -8090,12 +8097,12 @@ public class AudioService extends IAudioService.Stub
if (pcb == null) {
return;
}
- unregisterAudioPolicyInt(pcb);
+ unregisterAudioPolicyInt(pcb, "unregisterAudioPolicy");
}
- private void unregisterAudioPolicyInt(@NonNull IAudioPolicyCallback pcb) {
- mDynPolicyLogger.log((new AudioEventLogger.StringEvent("unregisterAudioPolicyAsync for "
+ private void unregisterAudioPolicyInt(@NonNull IAudioPolicyCallback pcb, String operationName) {
+ mDynPolicyLogger.log((new AudioEventLogger.StringEvent(operationName + " for "
+ pcb.asBinder()).printLog(TAG)));
synchronized (mAudioPolicies) {
AudioPolicyProxy app = mAudioPolicies.remove(pcb.asBinder());
@@ -8570,7 +8577,8 @@ public class AudioService extends IAudioService.Stub
}
public void binderDied() {
- Log.i(TAG, "audio policy " + mPolicyCallback + " died");
+ mDynPolicyLogger.log((new AudioEventLogger.StringEvent("AudioPolicy "
+ + mPolicyCallback.asBinder() + " died").printLog(TAG)));
release();
}
diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java
index 4349ca451c36..2a74b3d23829 100644
--- a/services/core/java/com/android/server/slice/SliceManagerService.java
+++ b/services/core/java/com/android/server/slice/SliceManagerService.java
@@ -28,6 +28,8 @@ import static android.os.Process.SYSTEM_UID;
import android.Manifest.permission;
import android.annotation.NonNull;
import android.app.AppOpsManager;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
import android.app.slice.ISliceManager;
import android.app.slice.SliceSpec;
import android.app.usage.UsageStatsManagerInternal;
@@ -77,6 +79,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
public class SliceManagerService extends ISliceManager.Stub {
@@ -121,6 +124,7 @@ public class SliceManagerService extends ISliceManager.Stub {
filter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
+ mRoleObserver = new RoleObserver();
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
}
@@ -478,10 +482,26 @@ public class SliceManagerService extends ISliceManager.Stub {
return cn.getPackageName();
}
+ /**
+ * A cached value of the default home app
+ */
+ private String mCachedDefaultHome = null;
+
// Based on getDefaultHome in ShortcutService.
// TODO: Unify if possible
@VisibleForTesting
protected String getDefaultHome(int userId) {
+
+ // Set VERIFY to true to run the cache in "shadow" mode for cache
+ // testing. Do not commit set to true;
+ final boolean VERIFY = false;
+
+ if (mCachedDefaultHome != null) {
+ if (!VERIFY) {
+ return mCachedDefaultHome;
+ }
+ }
+
final long token = Binder.clearCallingIdentity();
try {
final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
@@ -490,10 +510,12 @@ public class SliceManagerService extends ISliceManager.Stub {
final ComponentName defaultLauncher = mPackageManagerInternal
.getHomeActivitiesAsUser(allHomeCandidates, userId);
- ComponentName detected = null;
- if (defaultLauncher != null) {
- detected = defaultLauncher;
- }
+ ComponentName detected = defaultLauncher;
+
+ // Cache the default launcher. It is not a problem if the
+ // launcher is null - eventually, the default launcher will be
+ // set to something non-null.
+ mCachedDefaultHome = ((detected != null) ? detected.getPackageName() : null);
if (detected == null) {
// If we reach here, that means it's the first check since the user was created,
@@ -517,12 +539,54 @@ public class SliceManagerService extends ISliceManager.Stub {
lastPriority = ri.priority;
}
}
- return detected != null ? detected.getPackageName() : null;
+ final String ret = ((detected != null) ? detected.getPackageName() : null);
+ if (VERIFY) {
+ if (mCachedDefaultHome != null && !mCachedDefaultHome.equals(ret)) {
+ Slog.e(TAG, "getDefaultHome() cache failure, is " +
+ mCachedDefaultHome + " should be " + ret);
+ }
+ }
+ return ret;
} finally {
Binder.restoreCallingIdentity(token);
}
}
+ public void invalidateCachedDefaultHome() {
+ mCachedDefaultHome = null;
+ }
+
+ /**
+ * Listen for changes in the roles, and invalidate the cached default
+ * home as necessary.
+ */
+ private RoleObserver mRoleObserver;
+
+ class RoleObserver implements OnRoleHoldersChangedListener {
+ private RoleManager mRm;
+ private final Executor mExecutor;
+
+ RoleObserver() {
+ mExecutor = mContext.getMainExecutor();
+ register();
+ }
+
+ public void register() {
+ mRm = mContext.getSystemService(RoleManager.class);
+ if (mRm != null) {
+ mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
+ invalidateCachedDefaultHome();
+ }
+ }
+
+ @Override
+ public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+ if (RoleManager.ROLE_HOME.equals(roleName)) {
+ invalidateCachedDefaultHome();
+ }
+ }
+ }
+
private boolean isGrantedFullAccess(String pkg, int userId) {
return mPermissions.hasFullAccess(pkg, userId);
}
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index f0618c23b025..b6633ced771b 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -231,6 +231,9 @@ bool isValidEffect(jlong effect) {
}
static void callVibrationOnComplete(jobject vibration) {
+ if (vibration == nullptr) {
+ return;
+ }
auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
jniEnv->CallVoidMethod(vibration, sMethodIdOnComplete);
jniEnv->DeleteGlobalRef(vibration);
@@ -281,18 +284,16 @@ static jboolean vibratorExists(JNIEnv* env, jclass /* clazz */, jlong controller
return controller->ping().isOk() ? JNI_TRUE : JNI_FALSE;
}
-static void vibratorOn(JNIEnv* /* env */, jclass /* clazz */, jlong timeout_ms) {
- if (auto hal = getHal<aidl::IVibrator>()) {
- auto status = hal->call(&aidl::IVibrator::on, timeout_ms, nullptr);
- if (!status.isOk()) {
- ALOGE("vibratorOn command failed: %s", status.toString8().string());
- }
- } else {
- Status retStatus = halCall(&V1_0::IVibrator::on, timeout_ms).withDefault(Status::UNKNOWN_ERROR);
- if (retStatus != Status::OK) {
- ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
- }
+static void vibratorOn(JNIEnv* env, jclass /* clazz */, jlong controllerPtr, jlong timeoutMs,
+ jobject vibration) {
+ vibrator::HalController* controller = reinterpret_cast<vibrator::HalController*>(controllerPtr);
+ if (controller == nullptr) {
+ ALOGE("vibratorOn failed because controller was not initialized");
+ return;
}
+ jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration);
+ auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); };
+ controller->on(std::chrono::milliseconds(timeoutMs), callback);
}
static void vibratorOff(JNIEnv* env, jclass /* clazz */, jlong controllerPtr) {
@@ -420,9 +421,8 @@ static void vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong
jobject element = env->GetObjectArrayElement(composition, i);
effects.push_back(effectFromJavaPrimitive(env, element));
}
- auto callback = [vibrationRef(MakeGlobalRefOrDie(env, vibration))]() {
- callVibrationOnComplete(vibrationRef);
- };
+ jobject vibrationRef = vibration == nullptr ? vibration : MakeGlobalRefOrDie(env, vibration);
+ auto callback = [vibrationRef]() { callVibrationOnComplete(vibrationRef); };
controller->performComposedEffect(effects, callback);
}
@@ -461,7 +461,7 @@ static const JNINativeMethod method_table[] = {
{"vibratorInit", "()J", (void*)vibratorInit},
{"vibratorGetFinalizer", "()J", (void*)vibratorGetFinalizer},
{"vibratorExists", "(J)Z", (void*)vibratorExists},
- {"vibratorOn", "(J)V", (void*)vibratorOn},
+ {"vibratorOn", "(JJLcom/android/server/VibratorService$Vibration;)V", (void*)vibratorOn},
{"vibratorOff", "(J)V", (void*)vibratorOff},
{"vibratorSetAmplitude", "(JI)V", (void*)vibratorSetAmplitude},
{"vibratorPerformEffect", "(JJLcom/android/server/VibratorService$Vibration;Z)J",
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 935dffe5021e..9751c46f93c9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -421,6 +421,10 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO
AutoMutex _l(mLock);
mLocked.viewports = viewports;
mLocked.pointerDisplayId = pointerDisplayId;
+ std::shared_ptr<PointerController> controller = mLocked.pointerController.lock();
+ if (controller != nullptr) {
+ controller->onDisplayViewportsUpdated(mLocked.viewports);
+ }
} // release lock
mInputManager->getReader()->requestRefreshConfiguration(
@@ -818,8 +822,8 @@ void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) {
}
bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN;
- controller->setInactivityTimeout(lightsOut ? PointerController::InactivityTimeout::SHORT
- : PointerController::InactivityTimeout::NORMAL);
+ controller->setInactivityTimeout(lightsOut ? InactivityTimeout::SHORT
+ : InactivityTimeout::NORMAL);
}
void NativeInputManager::setPointerSpeed(int32_t speed) {
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
index 1b6ac3c84210..7d6d90c4578c 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
@@ -26,7 +26,9 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.intThat;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -65,6 +67,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
@@ -277,7 +280,7 @@ public class VibratorServiceTest {
assertTrue(service.isVibrating());
verify(mNativeWrapperMock).vibratorOff();
- verify(mNativeWrapperMock).vibratorOn(eq(100L));
+ verify(mNativeWrapperMock).vibratorOn(eq(100L), any(VibratorService.Vibration.class));
verify(mNativeWrapperMock).vibratorSetAmplitude(eq(128));
}
@@ -290,7 +293,7 @@ public class VibratorServiceTest {
assertTrue(service.isVibrating());
verify(mNativeWrapperMock).vibratorOff();
- verify(mNativeWrapperMock).vibratorOn(eq(100L));
+ verify(mNativeWrapperMock).vibratorOn(eq(100L), any(VibratorService.Vibration.class));
verify(mNativeWrapperMock, never()).vibratorSetAmplitude(anyInt());
}
@@ -344,76 +347,157 @@ public class VibratorServiceTest {
Mockito.clearInvocations(mNativeWrapperMock);
VibrationEffect effect = VibrationEffect.createWaveform(
- new long[] { 10, 10, 10 }, new int[] { 100, 200, 50 }, -1);
+ new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
vibrate(service, effect);
verify(mNativeWrapperMock).vibratorOff();
+ // Wait for VibrateThread to turn vibrator ON with total timing and no callback.
Thread.sleep(5);
- verify(mNativeWrapperMock).vibratorOn(eq(30L));
+ verify(mNativeWrapperMock).vibratorOn(eq(30L), isNull());
+
+ // First amplitude set right away.
verify(mNativeWrapperMock).vibratorSetAmplitude(eq(100));
+ // Second amplitude set after first timing is finished.
Thread.sleep(10);
verify(mNativeWrapperMock).vibratorSetAmplitude(eq(200));
+ // Third amplitude set after second timing is finished.
Thread.sleep(10);
verify(mNativeWrapperMock).vibratorSetAmplitude(eq(50));
}
@Test
- public void vibrate_withCallback_finishesVibrationWhenCallbackTriggered() {
- mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ public void vibrate_withOneShotAndNativeCallbackTriggered_finishesVibration() {
+ doAnswer(invocation -> {
+ ((VibratorService.Vibration) invocation.getArgument(1)).onComplete();
+ return null;
+ }).when(mNativeWrapperMock).vibratorOn(anyLong(), any(VibratorService.Vibration.class));
+ VibratorService service = createService();
+ Mockito.clearInvocations(mNativeWrapperMock);
+
+ vibrate(service, VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
+
+ InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(100L),
+ any(VibratorService.Vibration.class));
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+ }
+
+ @Test
+ public void vibrate_withOneShotAndNativeCallbackNotTriggered_finishesVibrationViaFallback() {
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
+ vibrate(service, VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
+
+ verify(mNativeWrapperMock).vibratorOff();
+ verify(mNativeWrapperMock).vibratorOn(eq(100L), any(VibratorService.Vibration.class));
+ Mockito.clearInvocations(mNativeWrapperMock);
+
+ // Run the scheduled callback to finish one-shot vibration.
+ mTestLooper.moveTimeForward(200);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock).vibratorOff();
+ }
+
+ @Test
+ public void vibrate_withWaveformAndNativeCallback_callbackCannotBeTriggeredByNative()
+ throws Exception {
+ VibratorService service = createService();
+ Mockito.clearInvocations(mNativeWrapperMock);
+
+ VibrationEffect effect = VibrationEffect.createWaveform(new long[]{1, 3, 1, 2}, -1);
+ vibrate(service, effect);
+
+ // Wait for VibrateThread to finish: 1ms OFF, 3ms ON, 1ms OFF, 2ms ON.
+ Thread.sleep(15);
+ InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(3L), isNull());
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(2L), isNull());
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+ }
+
+ @Test
+ public void vibrate_withComposedAndNativeCallbackTriggered_finishesVibration() {
+ mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
doAnswer(invocation -> {
((VibratorService.Vibration) invocation.getArgument(1)).onComplete();
return null;
}).when(mNativeWrapperMock).vibratorPerformComposedEffect(
any(), any(VibratorService.Vibration.class));
+ VibratorService service = createService();
+ Mockito.clearInvocations(mNativeWrapperMock);
- // Use vibration with delay so there is time for the callback to be triggered.
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10)
.compose();
vibrate(service, effect);
- // Vibration canceled once before perform and once by native callback.
- verify(mNativeWrapperMock, times(2)).vibratorOff();
- verify(mNativeWrapperMock).vibratorPerformComposedEffect(
+ InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorPerformComposedEffect(
any(VibrationEffect.Composition.PrimitiveEffect[].class),
any(VibratorService.Vibration.class));
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
}
@Test
- public void vibrate_whenBinderDies_cancelsVibration() {
+ public void vibrate_withComposedAndNativeCallbackNotTriggered_finishesVibrationViaFallback() {
mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
VibratorService service = createService();
Mockito.clearInvocations(mNativeWrapperMock);
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10)
+ .compose();
+ vibrate(service, effect);
+
+ verify(mNativeWrapperMock).vibratorOff();
+ verify(mNativeWrapperMock).vibratorPerformComposedEffect(
+ any(VibrationEffect.Composition.PrimitiveEffect[].class),
+ any(VibratorService.Vibration.class));
+ Mockito.clearInvocations(mNativeWrapperMock);
+
+ // Run the scheduled callback to finish one-shot vibration.
+ mTestLooper.moveTimeForward(10000); // 10s
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock).vibratorOff();
+ }
+
+ @Test
+ public void vibrate_whenBinderDies_cancelsVibration() {
+ mockVibratorCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
doAnswer(invocation -> {
((VibratorService.Vibration) invocation.getArgument(1)).binderDied();
return null;
}).when(mNativeWrapperMock).vibratorPerformComposedEffect(
any(), any(VibratorService.Vibration.class));
+ VibratorService service = createService();
+ Mockito.clearInvocations(mNativeWrapperMock);
- // Use vibration with delay so there is time for the callback to be triggered.
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 10)
.compose();
vibrate(service, effect);
- // Vibration canceled once before perform and once by native binder death.
- verify(mNativeWrapperMock, times(2)).vibratorOff();
- verify(mNativeWrapperMock).vibratorPerformComposedEffect(
+ InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorPerformComposedEffect(
any(VibrationEffect.Composition.PrimitiveEffect[].class),
any(VibratorService.Vibration.class));
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
}
@Test
public void cancelVibrate_withDeviceVibrating_callsVibratorOff() {
VibratorService service = createService();
- vibrate(service, VibrationEffect.createOneShot(100, 128));
+ vibrate(service, VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
assertTrue(service.isVibrating());
Mockito.clearInvocations(mNativeWrapperMock);
@@ -434,18 +518,20 @@ public class VibratorServiceTest {
@Test
public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
+ doAnswer(invocation -> {
+ ((VibratorService.Vibration) invocation.getArgument(1)).onComplete();
+ return null;
+ }).when(mNativeWrapperMock).vibratorOn(anyLong(), any(VibratorService.Vibration.class));
VibratorService service = createService();
service.registerVibratorStateListener(mVibratorStateListenerMock);
verify(mVibratorStateListenerMock).onVibrating(false);
+ Mockito.clearInvocations(mVibratorStateListenerMock);
vibrate(service, VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE));
- verify(mVibratorStateListenerMock).onVibrating(true);
-
- // Run the scheduled callback to finish one-shot vibration.
- mTestLooper.moveTimeForward(10);
- mTestLooper.dispatchAll();
- verify(mVibratorStateListenerMock, times(2)).onVibrating(false);
+ InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
+ inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
+ inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
}
@Test