Merge "[CredManUI] Make all bottom sheet pages scrollable." into udc-dev
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index a99815c..6301ad7 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -556,12 +556,7 @@
@Override
public ActivityInfo getActivityInfo(ComponentName className, ComponentInfoFlags flags)
throws NameNotFoundException {
- return getActivityInfoAsUser(className, flags, getUserId());
- }
-
- @Override
- public ActivityInfo getActivityInfoAsUser(ComponentName className,
- ComponentInfoFlags flags, @UserIdInt int userId) throws NameNotFoundException {
+ final int userId = getUserId();
try {
ActivityInfo ai = mPM.getActivityInfo(className,
updateFlagsForComponent(flags.getValue(), userId, null), userId);
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index bd3cf5f..afc2285 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -20,9 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.app.compat.CompatChanges;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Build;
@@ -185,28 +182,6 @@
private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
/**
- * An intent with action set as null used to always pass the action test during intent
- * filter matching. This causes a lot of confusion and unexpected intent matches.
- * Null action intents should be blocked when either the intent sender or receiver
- * application targets U or higher.
- *
- * mBlockNullAction indicates whether the intent filter owner (intent receiver) is
- * targeting U+. This value will be properly set by package manager when IntentFilters are
- * passed to an application, so that when an application is trying to perform intent filter
- * matching locally, the correct matching algorithm will be chosen.
- *
- * When an IntentFilter is sent to system server (e.g. for registering runtime receivers),
- * the value set in mBlockNullAction will be ignored and overwritten with the correct
- * value evaluated based on the Binder calling identity. This makes sure that the
- * security enforcement cannot be bypassed by crafting a malicious IntentFilter.
- *
- * @hide
- */
- @ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
- public static final long BLOCK_NULL_ACTION_INTENTS = 264497795;
-
- /**
* The filter {@link #setPriority} value at which system high-priority
* receivers are placed; that is, receivers that should execute before
* application code. Applications should never use filters with this or
@@ -2301,7 +2276,6 @@
String type = resolve ? intent.resolveType(resolver) : intent.getType();
return match(intent.getAction(), type, intent.getScheme(),
intent.getData(), intent.getCategories(), logTag,
- CompatChanges.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS),
false /* supportWildcards */, null /* ignoreActions */,
intent.getExtras());
}
@@ -2354,7 +2328,6 @@
Uri data, Set<String> categories, String logTag, boolean supportWildcards,
@Nullable Collection<String> ignoreActions) {
return match(action, type, scheme, data, categories, logTag, supportWildcards,
- CompatChanges.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS),
ignoreActions, null /* extras */);
}
@@ -2366,10 +2339,8 @@
*/
public final int match(String action, String type, String scheme,
Uri data, Set<String> categories, String logTag, boolean supportWildcards,
- boolean blockNullAction, @Nullable Collection<String> ignoreActions,
- @Nullable Bundle extras) {
- if ((action == null && blockNullAction)
- || !matchAction(action, supportWildcards, ignoreActions)) {
+ @Nullable Collection<String> ignoreActions, @Nullable Bundle extras) {
+ if (action != null && !matchAction(action, supportWildcards, ignoreActions)) {
if (false) Log.v(
logTag, "No matching action " + action + " for " + this);
return NO_MATCH_ACTION;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index d927c5e..9388823 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5720,17 +5720,6 @@
}
/**
- * @hide
- */
- @NonNull
- public ActivityInfo getActivityInfoAsUser(@NonNull ComponentName component,
- @NonNull ComponentInfoFlags flags, @UserIdInt int userId)
- throws NameNotFoundException {
- throw new UnsupportedOperationException(
- "getActivityInfoAsUser not implemented in subclass");
- }
-
- /**
* Retrieve all of the information we know about a particular receiver
* class.
*
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index bf9d3c2..fd86769 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -34,8 +34,9 @@
* has create a starting window for the Task.
*
* @param info The information about the Task that's available
+ * @param appToken Token of the application being started.
*/
- void addStartingWindow(in StartingWindowInfo info);
+ void addStartingWindow(in StartingWindowInfo info, IBinder appToken);
/**
* Called when the Task want to remove the starting window.
diff --git a/core/java/android/window/IWindowlessStartingSurfaceCallback.aidl b/core/java/android/window/IWindowlessStartingSurfaceCallback.aidl
deleted file mode 100644
index a081356..0000000
--- a/core/java/android/window/IWindowlessStartingSurfaceCallback.aidl
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.window;
-
-import android.view.SurfaceControl;
-
-/**
- * Interface to be invoked when a windowless starting surface added.
- *
- * @param addedSurface The starting surface.
- * {@hide}
- */
-interface IWindowlessStartingSurfaceCallback {
- void onSurfaceAdded(in SurfaceControl addedSurface);
-}
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 071c20f..1a58fd5 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -329,21 +329,6 @@
}
/**
- * Get or create a TaskDescription from a RunningTaskInfo.
- */
- public static ActivityManager.TaskDescription getOrCreateTaskDescription(
- ActivityManager.RunningTaskInfo runningTaskInfo) {
- final ActivityManager.TaskDescription taskDescription;
- if (runningTaskInfo.taskDescription != null) {
- taskDescription = runningTaskInfo.taskDescription;
- } else {
- taskDescription = new ActivityManager.TaskDescription();
- taskDescription.setBackgroundColor(WHITE);
- }
- return taskDescription;
- }
-
- /**
* Help method to draw the snapshot on a surface.
*/
public static void drawSnapshotOnSurface(StartingWindowInfo info, WindowManager.LayoutParams lp,
@@ -359,8 +344,13 @@
final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams;
final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
- final ActivityManager.TaskDescription taskDescription =
- getOrCreateTaskDescription(runningTaskInfo);
+ final ActivityManager.TaskDescription taskDescription;
+ if (runningTaskInfo.taskDescription != null) {
+ taskDescription = runningTaskInfo.taskDescription;
+ } else {
+ taskDescription = new ActivityManager.TaskDescription();
+ taskDescription.setBackgroundColor(WHITE);
+ }
drawSurface.initiateSystemBarPainter(lp.flags, lp.privateFlags,
attrs.insetsFlags.appearance, taskDescription, info.requestedVisibleTypes);
final Rect systemBarInsets = getSystemBarInsets(windowBounds, topWindowInsetsState);
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index 451acbe..1b64e61 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -22,12 +22,9 @@
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.pm.ActivityInfo;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.RemoteException;
import android.view.InsetsState;
-import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
@@ -62,8 +59,6 @@
/** @hide **/
public static final int STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN = 4;
- public static final int STARTING_WINDOW_TYPE_WINDOWLESS = 5;
-
/**
* @hide
*/
@@ -72,8 +67,7 @@
STARTING_WINDOW_TYPE_SPLASH_SCREEN,
STARTING_WINDOW_TYPE_SNAPSHOT,
STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN,
- STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN,
- STARTING_WINDOW_TYPE_WINDOWLESS
+ STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
})
public @interface StartingWindowType {}
@@ -124,7 +118,6 @@
TYPE_PARAMETER_ACTIVITY_CREATED,
TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN,
TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN,
- TYPE_PARAMETER_WINDOWLESS,
TYPE_PARAMETER_LEGACY_SPLASH_SCREEN
})
public @interface StartingTypeParams {}
@@ -158,12 +151,6 @@
* @hide
*/
public static final int TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN = 0x00000080;
-
- /**
- * Windowless surface
- */
- public static final int TYPE_PARAMETER_WINDOWLESS = 0x00000100;
-
/**
* Application is allowed to use the legacy splash screen
* @hide
@@ -195,33 +182,7 @@
*/
public TaskSnapshot taskSnapshot;
- @InsetsType public int requestedVisibleTypes = WindowInsets.Type.defaultVisible();
-
- /**
- * App token where the starting window should add to.
- */
- public IBinder appToken;
-
- public IWindowlessStartingSurfaceCallback windowlessStartingSurfaceCallback;
-
- /**
- * The root surface where windowless surface should attach on.
- */
- public SurfaceControl rootSurface;
-
- /**
- * Notify windowless surface is created.
- * @param addedSurface Created surface.
- */
- public void notifyAddComplete(SurfaceControl addedSurface) {
- if (windowlessStartingSurfaceCallback != null) {
- try {
- windowlessStartingSurfaceCallback.onSurfaceAdded(addedSurface);
- } catch (RemoteException e) {
- //
- }
- }
- }
+ public @InsetsType int requestedVisibleTypes = WindowInsets.Type.defaultVisible();
public StartingWindowInfo() {
@@ -255,9 +216,6 @@
dest.writeBoolean(isKeyguardOccluded);
dest.writeTypedObject(taskSnapshot, flags);
dest.writeInt(requestedVisibleTypes);
- dest.writeStrongBinder(appToken);
- dest.writeStrongInterface(windowlessStartingSurfaceCallback);
- dest.writeTypedObject(rootSurface, flags);
}
void readFromParcel(@NonNull Parcel source) {
@@ -272,10 +230,6 @@
isKeyguardOccluded = source.readBoolean();
taskSnapshot = source.readTypedObject(TaskSnapshot.CREATOR);
requestedVisibleTypes = source.readInt();
- appToken = source.readStrongBinder();
- windowlessStartingSurfaceCallback = IWindowlessStartingSurfaceCallback.Stub
- .asInterface(source.readStrongBinder());
- rootSurface = source.readTypedObject(SurfaceControl.CREATOR);
}
@Override
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 5181236..384dacf 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -67,16 +67,6 @@
*/
public float roundedCornerRadius;
- /**
- * Remove windowless surface.
- */
- public boolean windowlessSurface;
-
- /**
- * Remove immediately.
- */
- public boolean removeImmediately;
-
public StartingWindowRemovalInfo() {
}
@@ -97,8 +87,6 @@
playRevealAnimation = source.readBoolean();
deferRemoveForIme = source.readBoolean();
roundedCornerRadius = source.readFloat();
- windowlessSurface = source.readBoolean();
- removeImmediately = source.readBoolean();
}
@Override
@@ -109,8 +97,6 @@
dest.writeBoolean(playRevealAnimation);
dest.writeBoolean(deferRemoveForIme);
dest.writeFloat(roundedCornerRadius);
- dest.writeBoolean(windowlessSurface);
- dest.writeBoolean(removeImmediately);
}
@Override
@@ -119,9 +105,7 @@
+ " frame=" + mainFrame
+ " playRevealAnimation=" + playRevealAnimation
+ " roundedCornerRadius=" + roundedCornerRadius
- + " deferRemoveForIme=" + deferRemoveForIme
- + " windowlessSurface=" + windowlessSurface
- + " removeImmediately=" + removeImmediately + "}";
+ + " deferRemoveForIme=" + deferRemoveForIme + "}";
}
public static final @android.annotation.NonNull Creator<StartingWindowRemovalInfo> CREATOR =
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index d4728c1..02878f8 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -92,10 +92,13 @@
* has create a starting window for the Task.
*
* @param info The information about the Task that's available
+ * @param appToken Token of the application being started.
+ * context to for resources
* @hide
*/
@BinderThread
- public void addStartingWindow(@NonNull StartingWindowInfo info) {}
+ public void addStartingWindow(@NonNull StartingWindowInfo info,
+ @NonNull IBinder appToken) {}
/**
* Called when the Task want to remove the starting window.
@@ -294,8 +297,9 @@
private final ITaskOrganizer mInterface = new ITaskOrganizer.Stub() {
@Override
- public void addStartingWindow(StartingWindowInfo windowInfo) {
- mExecutor.execute(() -> TaskOrganizer.this.addStartingWindow(windowInfo));
+ public void addStartingWindow(StartingWindowInfo windowInfo,
+ IBinder appToken) {
+ mExecutor.execute(() -> TaskOrganizer.this.addStartingWindow(windowInfo, appToken));
}
@Override
diff --git a/core/java/com/android/internal/midi/EventScheduler.java b/core/java/com/android/internal/midi/EventScheduler.java
index 506902f6..426cd74 100644
--- a/core/java/com/android/internal/midi/EventScheduler.java
+++ b/core/java/com/android/internal/midi/EventScheduler.java
@@ -16,7 +16,6 @@
package com.android.internal.midi;
-import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;
@@ -26,11 +25,11 @@
* And only one Thread can read from the buffer.
*/
public class EventScheduler {
- private static final long NANOS_PER_MILLI = 1000000;
+ public static final long NANOS_PER_MILLI = 1000000;
private final Object mLock = new Object();
- volatile private SortedMap<Long, FastEventQueue> mEventBuffer;
- private FastEventQueue mEventPool = null;
+ protected volatile SortedMap<Long, FastEventQueue> mEventBuffer;
+ protected FastEventQueue mEventPool = null;
private int mMaxPoolSize = 200;
private boolean mClosed;
@@ -38,9 +37,13 @@
mEventBuffer = new TreeMap<Long, FastEventQueue>();
}
- // If we keep at least one node in the list then it can be atomic
- // and non-blocking.
- private class FastEventQueue {
+ /**
+ * Class for a fast event queue.
+ *
+ * If we keep at least one node in the list then it can be atomic
+ * and non-blocking.
+ */
+ public static class FastEventQueue {
// One thread takes from the beginning of the list.
volatile SchedulableEvent mFirst;
// A second thread returns events to the end of the list.
@@ -48,7 +51,7 @@
volatile long mEventsAdded;
volatile long mEventsRemoved;
- FastEventQueue(SchedulableEvent event) {
+ public FastEventQueue(SchedulableEvent event) {
mFirst = event;
mLast = mFirst;
mEventsAdded = 1;
@@ -149,7 +152,8 @@
* @param event
*/
public void add(SchedulableEvent event) {
- synchronized (mLock) {
+ Object lock = getLock();
+ synchronized (lock) {
FastEventQueue list = mEventBuffer.get(event.getTimestamp());
if (list == null) {
long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE
@@ -159,7 +163,7 @@
// If the event we added is earlier than the previous earliest
// event then notify any threads waiting for the next event.
if (event.getTimestamp() < lowestTime) {
- mLock.notify();
+ lock.notify();
}
} else {
list.add(event);
@@ -167,7 +171,7 @@
}
}
- private SchedulableEvent removeNextEventLocked(long lowestTime) {
+ protected SchedulableEvent removeNextEventLocked(long lowestTime) {
SchedulableEvent event;
FastEventQueue list = mEventBuffer.get(lowestTime);
// Remove list from tree if this is the last node.
@@ -186,7 +190,8 @@
*/
public SchedulableEvent getNextEvent(long time) {
SchedulableEvent event = null;
- synchronized (mLock) {
+ Object lock = getLock();
+ synchronized (lock) {
if (!mEventBuffer.isEmpty()) {
long lowestTime = mEventBuffer.firstKey();
// Is it time for this list to be processed?
@@ -209,7 +214,8 @@
*/
public SchedulableEvent waitNextEvent() throws InterruptedException {
SchedulableEvent event = null;
- synchronized (mLock) {
+ Object lock = getLock();
+ synchronized (lock) {
while (!mClosed) {
long millisToWait = Integer.MAX_VALUE;
if (!mEventBuffer.isEmpty()) {
@@ -231,7 +237,7 @@
}
}
}
- mLock.wait((int) millisToWait);
+ lock.wait((int) millisToWait);
}
}
return event;
@@ -242,10 +248,25 @@
mEventBuffer = new TreeMap<Long, FastEventQueue>();
}
+ /**
+ * Stops the EventScheduler.
+ * The subscriber calling waitNextEvent() will get one final SchedulableEvent returning null.
+ */
public void close() {
- synchronized (mLock) {
+ Object lock = getLock();
+ synchronized (lock) {
mClosed = true;
- mLock.notify();
+ lock.notify();
}
}
+
+ /**
+ * Gets the lock. This doesn't lock it in anyway.
+ * Subclasses can override this.
+ *
+ * @return Object
+ */
+ protected Object getLock() {
+ return mLock;
+ }
}
diff --git a/core/java/com/android/internal/midi/MidiEventMultiScheduler.java b/core/java/com/android/internal/midi/MidiEventMultiScheduler.java
new file mode 100644
index 0000000..16e4abe
--- /dev/null
+++ b/core/java/com/android/internal/midi/MidiEventMultiScheduler.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.midi;
+
+/**
+ * Uses multiple MidiEventSchedulers for waiting for events.
+ *
+ */
+public class MidiEventMultiScheduler {
+ private MultiLockMidiEventScheduler[] mMidiEventSchedulers;
+ private int mNumEventSchedulers;
+ private int mNumClosedSchedulers = 0;
+ private final Object mMultiLock = new Object();
+
+ private class MultiLockMidiEventScheduler extends MidiEventScheduler {
+ @Override
+ public void close() {
+ synchronized (mMultiLock) {
+ mNumClosedSchedulers++;
+ }
+ super.close();
+ }
+
+ @Override
+ protected Object getLock() {
+ return mMultiLock;
+ }
+
+ public boolean isEventBufferEmptyLocked() {
+ return mEventBuffer.isEmpty();
+ }
+
+ public long getLowestTimeLocked() {
+ return mEventBuffer.firstKey();
+ }
+ }
+
+ /**
+ * MidiEventMultiScheduler constructor
+ *
+ * @param numSchedulers the number of schedulers to create
+ */
+ public MidiEventMultiScheduler(int numSchedulers) {
+ mNumEventSchedulers = numSchedulers;
+ mMidiEventSchedulers = new MultiLockMidiEventScheduler[numSchedulers];
+ for (int i = 0; i < numSchedulers; i++) {
+ mMidiEventSchedulers[i] = new MultiLockMidiEventScheduler();
+ }
+ }
+
+ /**
+ * Waits for the next MIDI event. This will return true when it receives it.
+ * If all MidiEventSchedulers have been closed, this will return false.
+ *
+ * @return true if a MIDI event is received and false if all schedulers are closed.
+ */
+ public boolean waitNextEvent() throws InterruptedException {
+ synchronized (mMultiLock) {
+ while (true) {
+ if (mNumClosedSchedulers >= mNumEventSchedulers) {
+ return false;
+ }
+ long lowestTime = Long.MAX_VALUE;
+ long now = System.nanoTime();
+ for (MultiLockMidiEventScheduler eventScheduler : mMidiEventSchedulers) {
+ if (!eventScheduler.isEventBufferEmptyLocked()) {
+ lowestTime = Math.min(lowestTime,
+ eventScheduler.getLowestTimeLocked());
+ }
+ }
+ if (lowestTime <= now) {
+ return true;
+ }
+ long nanosToWait = lowestTime - now;
+ // Add 1 millisecond so we don't wake up before it is
+ // ready.
+ long millisToWait = 1 + (nanosToWait / EventScheduler.NANOS_PER_MILLI);
+ // Clip 64-bit value to 32-bit max.
+ if (millisToWait > Integer.MAX_VALUE) {
+ millisToWait = Integer.MAX_VALUE;
+ }
+ mMultiLock.wait(millisToWait);
+ }
+ }
+ }
+
+ /**
+ * Gets the number of MidiEventSchedulers.
+ *
+ * @return the number of MidiEventSchedulers.
+ */
+ public int getNumEventSchedulers() {
+ return mNumEventSchedulers;
+ }
+
+ /**
+ * Gets a specific MidiEventScheduler based on the index.
+ *
+ * @param index the zero indexed index of a MIDI event scheduler
+ * @return a MidiEventScheduler
+ */
+ public MidiEventScheduler getEventScheduler(int index) {
+ return mMidiEventSchedulers[index];
+ }
+
+ /**
+ * Closes all event schedulers.
+ */
+ public void close() {
+ for (MidiEventScheduler eventScheduler : mMidiEventSchedulers) {
+ eventScheduler.close();
+ }
+ }
+}
diff --git a/core/java/com/android/internal/midi/MidiEventScheduler.java b/core/java/com/android/internal/midi/MidiEventScheduler.java
index 7b01904..1b2934d 100644
--- a/core/java/com/android/internal/midi/MidiEventScheduler.java
+++ b/core/java/com/android/internal/midi/MidiEventScheduler.java
@@ -79,7 +79,7 @@
/**
* Create an event that contains the message.
*/
- private MidiEvent createScheduledEvent(byte[] msg, int offset, int count,
+ public MidiEvent createScheduledEvent(byte[] msg, int offset, int count,
long timestamp) {
MidiEvent event;
if (count > POOL_EVENT_SIZE) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index b6fd0bb..585f81c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -428,9 +428,9 @@
}
@Override
- public void addStartingWindow(StartingWindowInfo info) {
+ public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
if (mStartingWindow != null) {
- mStartingWindow.addStartingWindow(info);
+ mStartingWindow.addStartingWindow(info, appToken);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java
deleted file mode 100644
index 1ddd8f9..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.startingsurface;
-
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.hardware.display.DisplayManager;
-import android.view.Display;
-
-import com.android.wm.shell.common.ShellExecutor;
-
-// abstract class to create splash screen window(or windowless window)
-abstract class AbsSplashWindowCreator {
- protected static final String TAG = StartingWindowController.TAG;
- protected final SplashscreenContentDrawer mSplashscreenContentDrawer;
- protected final Context mContext;
- protected final DisplayManager mDisplayManager;
- protected final ShellExecutor mSplashScreenExecutor;
- protected final StartingSurfaceDrawer.StartingWindowRecordManager mStartingWindowRecordManager;
-
- private StartingSurface.SysuiProxy mSysuiProxy;
-
- AbsSplashWindowCreator(SplashscreenContentDrawer contentDrawer, Context context,
- ShellExecutor splashScreenExecutor, DisplayManager displayManager,
- StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
- mSplashscreenContentDrawer = contentDrawer;
- mContext = context;
- mSplashScreenExecutor = splashScreenExecutor;
- mDisplayManager = displayManager;
- mStartingWindowRecordManager = startingWindowRecordManager;
- }
-
- int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
- return splashScreenThemeResId != 0
- ? splashScreenThemeResId
- : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
- : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
- }
-
- protected Display getDisplay(int displayId) {
- return mDisplayManager.getDisplay(displayId);
- }
-
- void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) {
- mSysuiProxy = sysuiProxy;
- }
-
- protected void requestTopUi(boolean requestTopUi) {
- if (mSysuiProxy != null) {
- mSysuiProxy.requestTopUi(requestTopUi, TAG);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java
deleted file mode 100644
index 20c4d5a..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.startingsurface;
-
-import android.window.StartingWindowInfo;
-import android.window.TaskSnapshot;
-
-import com.android.wm.shell.common.ShellExecutor;
-
-class SnapshotWindowCreator {
- private final ShellExecutor mMainExecutor;
- private final StartingSurfaceDrawer.StartingWindowRecordManager
- mStartingWindowRecordManager;
-
- SnapshotWindowCreator(ShellExecutor mainExecutor,
- StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
- mMainExecutor = mainExecutor;
- mStartingWindowRecordManager = startingWindowRecordManager;
- }
-
- void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) {
- final int taskId = startingWindowInfo.taskInfo.taskId;
- // Remove any existing starting window for this task before adding.
- mStartingWindowRecordManager.removeWindow(taskId, true);
- final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo,
- startingWindowInfo.appToken, snapshot, mMainExecutor,
- () -> mStartingWindowRecordManager.removeWindow(taskId, true));
- if (surface != null) {
- final SnapshotWindowRecord tView = new SnapshotWindowRecord(surface,
- startingWindowInfo.taskInfo.topActivityType, mMainExecutor);
- mStartingWindowRecordManager.addRecord(taskId, tView);
- }
- }
-
- private static class SnapshotWindowRecord extends StartingSurfaceDrawer.SnapshotRecord {
- private final TaskSnapshotWindow mTaskSnapshotWindow;
-
- SnapshotWindowRecord(TaskSnapshotWindow taskSnapshotWindow,
- int activityType, ShellExecutor removeExecutor) {
- super(activityType, removeExecutor);
- mTaskSnapshotWindow = taskSnapshotWindow;
- mBGColor = mTaskSnapshotWindow.getBackgroundColor();
- }
-
- @Override
- protected void removeImmediately() {
- super.removeImmediately();
- mTaskSnapshotWindow.removeImmediately();
- }
-
- @Override
- protected boolean hasImeSurface() {
- return mTaskSnapshotWindow.hasImeSurface();
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 79cd891..ebb957b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -24,6 +24,9 @@
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MAX_ANIMATION_DURATION;
+import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MINIMAL_ANIMATION_DURATION;
+
import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -93,25 +96,6 @@
public class SplashscreenContentDrawer {
private static final String TAG = StartingWindowController.TAG;
- /**
- * The minimum duration during which the splash screen is shown when the splash screen icon is
- * animated.
- */
- static final long MINIMAL_ANIMATION_DURATION = 400L;
-
- /**
- * Allow the icon style splash screen to be displayed for longer to give time for the animation
- * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly
- * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration.
- */
- static final long TIME_WINDOW_DURATION = 100L;
-
- /**
- * The maximum duration during which the splash screen will be shown if the application is ready
- * to show before the icon animation finishes.
- */
- static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
-
// The acceptable area ratio of foreground_icon_area/background_icon_area, if there is an
// icon which it's non-transparent foreground area is similar to it's background area, then
// do not enlarge the foreground drawable.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
deleted file mode 100644
index 4db81e2..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
+++ /dev/null
@@ -1,506 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.startingsurface;
-
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.ActivityThread;
-import android.app.TaskInfo;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.hardware.display.DisplayManager;
-import android.os.IBinder;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.WindowInsetsController;
-import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
-import android.widget.FrameLayout;
-import android.window.SplashScreenView;
-import android.window.StartingWindowInfo;
-import android.window.StartingWindowRemovalInfo;
-
-import com.android.internal.R;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.ContrastColorUtil;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
-
-import java.util.function.Supplier;
-
-/**
- * A class which able to draw splash screen as the starting window for a task.
- *
- * In order to speed up, there will use two threads to creating a splash screen in parallel.
- * Right now we are still using PhoneWindow to create splash screen window, so the view is added to
- * the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call
- * scheduleTraversal to register a callback from Choreographer, so the drawing result of the view
- * can synchronize on each frame.
- *
- * The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing
- * the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background
- * thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after
- * the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very
- * quickly.
- *
- * So basically we are using the spare time to prepare the SplashScreenView while splash screen
- * thread is waiting for
- * 1. WindowManager#addView(binder call to WM),
- * 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device),
- * 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will
- * always happen before #draw).
- * Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on
- * splash-screen background tread can make they execute in parallel, which ensure it is faster then
- * to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame.
- *
- * Here is the sequence to compare the difference between using single and two thread.
- *
- * Single thread:
- * => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout
- * -> draw -> AdaptiveIconDrawable#draw
- *
- * Two threads:
- * => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw)
- * => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
- * directly).
- */
-class SplashscreenWindowCreator extends AbsSplashWindowCreator {
- private static final int LIGHT_BARS_MASK =
- WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
- | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-
- private final WindowManagerGlobal mWindowManagerGlobal;
- private Choreographer mChoreographer;
-
- /**
- * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is
- * rendered and that have not yet been removed by their client.
- */
- private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts =
- new SparseArray<>(1);
-
- SplashscreenWindowCreator(SplashscreenContentDrawer contentDrawer, Context context,
- ShellExecutor splashScreenExecutor, DisplayManager displayManager,
- StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
- super(contentDrawer, context, splashScreenExecutor, displayManager,
- startingWindowRecordManager);
- mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
- mWindowManagerGlobal = WindowManagerGlobal.getInstance();
- }
-
- void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
- @StartingWindowInfo.StartingWindowType int suggestType) {
- final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
- final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
- ? windowInfo.targetActivityInfo
- : taskInfo.topActivityInfo;
- if (activityInfo == null || activityInfo.packageName == null) {
- return;
- }
- // replace with the default theme if the application didn't set
- final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
- final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme,
- suggestType, mDisplayManager);
- if (context == null) {
- return;
- }
- final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(
- context, windowInfo, suggestType, activityInfo.packageName,
- suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
- ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, windowInfo.appToken);
-
- final int displayId = taskInfo.displayId;
- final int taskId = taskInfo.taskId;
- final Display display = getDisplay(displayId);
-
- // TODO(b/173975965) tracking performance
- // Prepare the splash screen content view on splash screen worker thread in parallel, so the
- // content view won't be blocked by binder call like addWindow and relayout.
- // 1. Trigger splash screen worker thread to create SplashScreenView before/while
- // Session#addWindow.
- // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start
- // traversal, which will call Session#relayout on splash screen thread.
- // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at
- // the same time the splash screen thread should be executing Session#relayout. Blocking the
- // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready.
-
- // Record whether create splash screen view success, notify to current thread after
- // create splash screen view finished.
- final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
- final FrameLayout rootLayout = new FrameLayout(
- mSplashscreenContentDrawer.createViewContextWrapper(context));
- rootLayout.setPadding(0, 0, 0, 0);
- rootLayout.setFitsSystemWindows(false);
- final Runnable setViewSynchronized = () -> {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
- // waiting for setContentView before relayoutWindow
- SplashScreenView contentView = viewSupplier.get();
- final StartingSurfaceDrawer.StartingWindowRecord sRecord =
- mStartingWindowRecordManager.getRecord(taskId);
- final SplashWindowRecord record = sRecord instanceof SplashWindowRecord
- ? (SplashWindowRecord) sRecord : null;
- // If record == null, either the starting window added fail or removed already.
- // Do not add this view if the token is mismatch.
- if (record != null && windowInfo.appToken == record.mAppToken) {
- // if view == null then creation of content view was failed.
- if (contentView != null) {
- try {
- rootLayout.addView(contentView);
- } catch (RuntimeException e) {
- Slog.w(TAG, "failed set content view to starting window "
- + "at taskId: " + taskId, e);
- contentView = null;
- }
- }
- record.setSplashScreenView(contentView);
- }
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- };
- requestTopUi(true);
- mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
- viewSupplier::setView, viewSupplier::setUiThreadInitTask);
- try {
- if (addWindow(taskId, windowInfo.appToken, rootLayout, display, params, suggestType)) {
- // We use the splash screen worker thread to create SplashScreenView while adding
- // the window, as otherwise Choreographer#doFrame might be delayed on this thread.
- // And since Choreographer#doFrame won't happen immediately after adding the window,
- // if the view is not added to the PhoneWindow on the first #doFrame, the view will
- // not be rendered on the first frame. So here we need to synchronize the view on
- // the window before first round relayoutWindow, which will happen after insets
- // animation.
- mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
- final SplashWindowRecord record =
- (SplashWindowRecord) mStartingWindowRecordManager.getRecord(taskId);
- if (record != null) {
- record.parseAppSystemBarColor(context);
- // Block until we get the background color.
- final SplashScreenView contentView = viewSupplier.get();
- if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
- contentView.addOnAttachStateChangeListener(
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- final int lightBarAppearance =
- ContrastColorUtil.isColorLight(
- contentView.getInitBackgroundColor())
- ? LIGHT_BARS_MASK : 0;
- contentView.getWindowInsetsController()
- .setSystemBarsAppearance(
- lightBarAppearance, LIGHT_BARS_MASK);
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- }
- });
- }
- }
- } else {
- // release the icon view host
- final SplashScreenView contentView = viewSupplier.get();
- if (contentView.getSurfaceHost() != null) {
- SplashScreenView.releaseIconHost(contentView.getSurfaceHost());
- }
- }
- } catch (RuntimeException e) {
- // don't crash if something else bad happens, for example a
- // failure loading resources because we are loading from an app
- // on external storage that has been unmounted.
- Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e);
- }
- }
-
- int estimateTaskBackgroundColor(TaskInfo taskInfo) {
- if (taskInfo.topActivityInfo == null) {
- return Color.TRANSPARENT;
- }
- final ActivityInfo activityInfo = taskInfo.topActivityInfo;
- final String packageName = activityInfo.packageName;
- final int userId = taskInfo.userId;
- final Context windowContext;
- try {
- windowContext = mContext.createPackageContextAsUser(
- packageName, Context.CONTEXT_RESTRICTED, UserHandle.of(userId));
- } catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Failed creating package context with package name "
- + packageName + " for user " + taskInfo.userId, e);
- return Color.TRANSPARENT;
- }
- try {
- final IPackageManager packageManager = ActivityThread.getPackageManager();
- final String splashScreenThemeName = packageManager.getSplashScreenTheme(packageName,
- userId);
- final int splashScreenThemeId = splashScreenThemeName != null
- ? windowContext.getResources().getIdentifier(splashScreenThemeName, null, null)
- : 0;
-
- final int theme = getSplashScreenTheme(splashScreenThemeId, activityInfo);
-
- if (theme != windowContext.getThemeResId()) {
- windowContext.setTheme(theme);
- }
- return mSplashscreenContentDrawer.estimateTaskBackgroundColor(windowContext);
- } catch (RuntimeException | RemoteException e) {
- Slog.w(TAG, "failed get starting window background color at taskId: "
- + taskInfo.taskId, e);
- }
- return Color.TRANSPARENT;
- }
-
- /**
- * Called when the Task wants to copy the splash screen.
- */
- public void copySplashScreenView(int taskId) {
- final StartingSurfaceDrawer.StartingWindowRecord record =
- mStartingWindowRecordManager.getRecord(taskId);
- final SplashWindowRecord preView = record instanceof SplashWindowRecord
- ? (SplashWindowRecord) record : null;
- SplashScreenView.SplashScreenViewParcelable parcelable;
- SplashScreenView splashScreenView = preView != null ? preView.mSplashView : null;
- if (splashScreenView != null && splashScreenView.isCopyable()) {
- parcelable = new SplashScreenView.SplashScreenViewParcelable(splashScreenView);
- parcelable.setClientCallback(
- new RemoteCallback((bundle) -> mSplashScreenExecutor.execute(
- () -> onAppSplashScreenViewRemoved(taskId, false))));
- splashScreenView.onCopied();
- mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost());
- } else {
- parcelable = null;
- }
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Copying splash screen window view for task: %d with parcelable %b",
- taskId, parcelable != null);
- ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
- }
-
- /**
- * Called when the {@link SplashScreenView} is removed from the client Activity view's hierarchy
- * or when the Activity is clean up.
- *
- * @param taskId The Task id on which the splash screen was attached
- */
- public void onAppSplashScreenViewRemoved(int taskId) {
- onAppSplashScreenViewRemoved(taskId, true /* fromServer */);
- }
-
- /**
- * @param fromServer If true, this means the removal was notified by the server. This is only
- * used for debugging purposes.
- * @see #onAppSplashScreenViewRemoved(int)
- */
- private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) {
- SurfaceControlViewHost viewHost =
- mAnimatedSplashScreenSurfaceHosts.get(taskId);
- if (viewHost == null) {
- return;
- }
- mAnimatedSplashScreenSurfaceHosts.remove(taskId);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "%s the splash screen. Releasing SurfaceControlViewHost for task: %d",
- fromServer ? "Server cleaned up" : "App removed", taskId);
- SplashScreenView.releaseIconHost(viewHost);
- }
-
- protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
- WindowManager.LayoutParams params,
- @StartingWindowInfo.StartingWindowType int suggestType) {
- boolean shouldSaveView = true;
- final Context context = view.getContext();
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
- mWindowManagerGlobal.addView(view, params, display,
- null /* parentWindow */, context.getUserId());
- } catch (WindowManager.BadTokenException e) {
- // ignore
- Slog.w(TAG, appToken + " already running, starting window not displayed. "
- + e.getMessage());
- shouldSaveView = false;
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- if (view.getParent() == null) {
- Slog.w(TAG, "view not successfully added to wm, removing view");
- mWindowManagerGlobal.removeView(view, true /* immediate */);
- shouldSaveView = false;
- }
- }
- if (shouldSaveView) {
- mStartingWindowRecordManager.removeWindow(taskId, true);
- saveSplashScreenRecord(appToken, taskId, view, suggestType);
- }
- return shouldSaveView;
- }
-
- private void saveSplashScreenRecord(IBinder appToken, int taskId, View view,
- @StartingWindowInfo.StartingWindowType int suggestType) {
- final SplashWindowRecord tView =
- new SplashWindowRecord(appToken, view, suggestType);
- mStartingWindowRecordManager.addRecord(taskId, tView);
- }
-
- private void removeWindowInner(View decorView, boolean hideView) {
- requestTopUi(false);
- if (hideView) {
- decorView.setVisibility(View.GONE);
- }
- mWindowManagerGlobal.removeView(decorView, false /* immediate */);
- }
-
- private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
- private SplashScreenView mView;
- private boolean mIsViewSet;
- private Runnable mUiThreadInitTask;
- void setView(SplashScreenView view) {
- synchronized (this) {
- mView = view;
- mIsViewSet = true;
- notify();
- }
- }
-
- void setUiThreadInitTask(Runnable initTask) {
- synchronized (this) {
- mUiThreadInitTask = initTask;
- }
- }
-
- @Override
- @Nullable
- public SplashScreenView get() {
- synchronized (this) {
- while (!mIsViewSet) {
- try {
- wait();
- } catch (InterruptedException ignored) {
- }
- }
- if (mUiThreadInitTask != null) {
- mUiThreadInitTask.run();
- mUiThreadInitTask = null;
- }
- return mView;
- }
- }
- }
-
- private class SplashWindowRecord extends StartingSurfaceDrawer.StartingWindowRecord {
- private final IBinder mAppToken;
- private final View mRootView;
- @StartingWindowInfo.StartingWindowType private final int mSuggestType;
- private final long mCreateTime;
-
- private boolean mSetSplashScreen;
- private SplashScreenView mSplashView;
- private int mSystemBarAppearance;
- private boolean mDrawsSystemBarBackgrounds;
-
- SplashWindowRecord(IBinder appToken, View decorView,
- @StartingWindowInfo.StartingWindowType int suggestType) {
- mAppToken = appToken;
- mRootView = decorView;
- mSuggestType = suggestType;
- mCreateTime = SystemClock.uptimeMillis();
- }
-
- void setSplashScreenView(SplashScreenView splashScreenView) {
- if (mSetSplashScreen) {
- return;
- }
- mSplashView = splashScreenView;
- mBGColor = mSplashView.getInitBackgroundColor();
- mSetSplashScreen = true;
- }
-
- void parseAppSystemBarColor(Context context) {
- final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
- mDrawsSystemBarBackgrounds = a.getBoolean(
- R.styleable.Window_windowDrawsSystemBarBackgrounds, false);
- if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
- mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
- }
- if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
- mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
- }
- a.recycle();
- }
-
- // Reset the system bar color which set by splash screen, make it align to the app.
- void clearSystemBarColor() {
- if (mRootView == null || !mRootView.isAttachedToWindow()) {
- return;
- }
- if (mRootView.getLayoutParams() instanceof WindowManager.LayoutParams) {
- final WindowManager.LayoutParams lp =
- (WindowManager.LayoutParams) mRootView.getLayoutParams();
- if (mDrawsSystemBarBackgrounds) {
- lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- } else {
- lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- }
- mRootView.setLayoutParams(lp);
- }
- mRootView.getWindowInsetsController().setSystemBarsAppearance(
- mSystemBarAppearance, LIGHT_BARS_MASK);
- }
-
- @Override
- public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
- if (mRootView != null) {
- if (mSplashView != null) {
- clearSystemBarColor();
- if (immediately
- || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
- removeWindowInner(mRootView, false);
- } else {
- if (info.playRevealAnimation) {
- mSplashscreenContentDrawer.applyExitAnimation(mSplashView,
- info.windowAnimationLeash, info.mainFrame,
- () -> removeWindowInner(mRootView, true),
- mCreateTime, info.roundedCornerRadius);
- } else {
- // the SplashScreenView has been copied to client, hide the view to skip
- // default exit animation
- removeWindowInner(mRootView, true);
- }
- }
- } else {
- // shouldn't happen
- Slog.e(TAG, "Found empty splash screen, remove!");
- removeWindowInner(mRootView, false);
- }
- }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index ff06db3..4f07bfe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -16,80 +16,169 @@
package com.android.wm.shell.startingsurface;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
-import android.annotation.CallSuper;
+import android.annotation.Nullable;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityTaskManager;
+import android.app.ActivityThread;
import android.app.TaskInfo;
-import android.app.WindowConfiguration;
import android.content.Context;
-import android.content.res.Configuration;
+import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
import android.graphics.Color;
+import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.util.Slog;
import android.util.SparseArray;
-import android.view.IWindow;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowInsetsController;
import android.view.WindowManager;
-import android.view.WindowlessWindowManager;
+import android.view.WindowManagerGlobal;
+import android.widget.FrameLayout;
import android.window.SplashScreenView;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.StartingWindowInfo;
import android.window.StartingWindowInfo.StartingWindowType;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskSnapshot;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ContrastColorUtil;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import java.util.function.Supplier;
+
/**
* A class which able to draw splash screen or snapshot as the starting window for a task.
+ *
+ * In order to speed up, there will use two threads to creating a splash screen in parallel.
+ * Right now we are still using PhoneWindow to create splash screen window, so the view is added to
+ * the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call
+ * scheduleTraversal to register a callback from Choreographer, so the drawing result of the view
+ * can synchronize on each frame.
+ *
+ * The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing
+ * the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background
+ * thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after
+ * the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very
+ * quickly.
+ *
+ * So basically we are using the spare time to prepare the SplashScreenView while splash screen
+ * thread is waiting for
+ * 1. WindowManager#addView(binder call to WM),
+ * 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device),
+ * 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will
+ * always happen before #draw).
+ * Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on
+ * splash-screen background tread can make they execute in parallel, which ensure it is faster then
+ * to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame.
+ *
+ * Here is the sequence to compare the difference between using single and two thread.
+ *
+ * Single thread:
+ * => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout
+ * -> draw -> AdaptiveIconDrawable#draw
+ *
+ * Two threads:
+ * => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw)
+ * => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
+ * directly).
*/
@ShellSplashscreenThread
public class StartingSurfaceDrawer {
+ private static final String TAG = StartingWindowController.TAG;
+ private final Context mContext;
+ private final DisplayManager mDisplayManager;
private final ShellExecutor mSplashScreenExecutor;
@VisibleForTesting
final SplashscreenContentDrawer mSplashscreenContentDrawer;
- @VisibleForTesting
- final SplashscreenWindowCreator mSplashscreenWindowCreator;
- private final SnapshotWindowCreator mSnapshotWindowCreator;
- private final WindowlessSplashWindowCreator mWindowlessSplashWindowCreator;
- private final WindowlessSnapshotWindowCreator mWindowlessSnapshotWindowCreator;
+ private Choreographer mChoreographer;
+ private final WindowManagerGlobal mWindowManagerGlobal;
+ private StartingSurface.SysuiProxy mSysuiProxy;
+ private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
- @VisibleForTesting
- final StartingWindowRecordManager mWindowRecords = new StartingWindowRecordManager();
- // Windowless surface could co-exist with starting window in a task.
- @VisibleForTesting
- final StartingWindowRecordManager mWindowlessRecords = new StartingWindowRecordManager();
+ private static final int LIGHT_BARS_MASK =
+ WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
+ | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+ /**
+ * The minimum duration during which the splash screen is shown when the splash screen icon is
+ * animated.
+ */
+ static final long MINIMAL_ANIMATION_DURATION = 400L;
+
+ /**
+ * Allow the icon style splash screen to be displayed for longer to give time for the animation
+ * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly
+ * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration.
+ */
+ static final long TIME_WINDOW_DURATION = 100L;
+
+ /**
+ * The maximum duration during which the splash screen will be shown if the application is ready
+ * to show before the icon animation finishes.
+ */
+ static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
+
/**
* @param splashScreenExecutor The thread used to control add and remove starting window.
*/
public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
IconProvider iconProvider, TransactionPool pool) {
+ mContext = context;
+ mDisplayManager = mContext.getSystemService(DisplayManager.class);
mSplashScreenExecutor = splashScreenExecutor;
- final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
- mSplashscreenContentDrawer = new SplashscreenContentDrawer(context, iconProvider, pool);
- displayManager.getDisplay(DEFAULT_DISPLAY);
+ mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool);
+ mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
+ mWindowManagerGlobal = WindowManagerGlobal.getInstance();
+ mDisplayManager.getDisplay(DEFAULT_DISPLAY);
+ }
- mSplashscreenWindowCreator = new SplashscreenWindowCreator(mSplashscreenContentDrawer,
- context, splashScreenExecutor, displayManager, mWindowRecords);
- mSnapshotWindowCreator = new SnapshotWindowCreator(splashScreenExecutor,
- mWindowRecords);
- mWindowlessSplashWindowCreator = new WindowlessSplashWindowCreator(
- mSplashscreenContentDrawer, context, splashScreenExecutor, displayManager,
- mWindowlessRecords, pool);
- mWindowlessSnapshotWindowCreator = new WindowlessSnapshotWindowCreator(
- mWindowlessRecords, context, displayManager, mSplashscreenContentDrawer, pool);
+ @VisibleForTesting
+ final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>();
+
+ /**
+ * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is
+ * rendered and that have not yet been removed by their client.
+ */
+ private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts =
+ new SparseArray<>(1);
+
+ private Display getDisplay(int displayId) {
+ return mDisplayManager.getDisplay(displayId);
+ }
+
+ int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
+ return splashScreenThemeResId != 0
+ ? splashScreenThemeResId
+ : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
+ : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
}
void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) {
- mSplashscreenWindowCreator.setSysuiProxy(sysuiProxy);
- mWindowlessSplashWindowCreator.setSysuiProxy(sysuiProxy);
+ mSysuiProxy = sysuiProxy;
}
/**
@@ -97,55 +186,231 @@
*
* @param suggestType The suggestion type to draw the splash screen.
*/
- void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
+ void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken,
@StartingWindowType int suggestType) {
- mSplashscreenWindowCreator.addSplashScreenStartingWindow(windowInfo, suggestType);
+ final RunningTaskInfo taskInfo = windowInfo.taskInfo;
+ final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+ ? windowInfo.targetActivityInfo
+ : taskInfo.topActivityInfo;
+ if (activityInfo == null || activityInfo.packageName == null) {
+ return;
+ }
+ // replace with the default theme if the application didn't set
+ final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
+ final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme,
+ suggestType, mDisplayManager);
+ if (context == null) {
+ return;
+ }
+ final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(
+ context, windowInfo, suggestType, activityInfo.packageName,
+ suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+ ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, appToken);
+
+ final int displayId = taskInfo.displayId;
+ final int taskId = taskInfo.taskId;
+ final Display display = getDisplay(displayId);
+
+ // TODO(b/173975965) tracking performance
+ // Prepare the splash screen content view on splash screen worker thread in parallel, so the
+ // content view won't be blocked by binder call like addWindow and relayout.
+ // 1. Trigger splash screen worker thread to create SplashScreenView before/while
+ // Session#addWindow.
+ // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start
+ // traversal, which will call Session#relayout on splash screen thread.
+ // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at
+ // the same time the splash screen thread should be executing Session#relayout. Blocking the
+ // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready.
+
+ // Record whether create splash screen view success, notify to current thread after
+ // create splash screen view finished.
+ final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
+ final FrameLayout rootLayout = new FrameLayout(
+ mSplashscreenContentDrawer.createViewContextWrapper(context));
+ rootLayout.setPadding(0, 0, 0, 0);
+ rootLayout.setFitsSystemWindows(false);
+ final Runnable setViewSynchronized = () -> {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
+ // waiting for setContentView before relayoutWindow
+ SplashScreenView contentView = viewSupplier.get();
+ final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+ // If record == null, either the starting window added fail or removed already.
+ // Do not add this view if the token is mismatch.
+ if (record != null && appToken == record.mAppToken) {
+ // if view == null then creation of content view was failed.
+ if (contentView != null) {
+ try {
+ rootLayout.addView(contentView);
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "failed set content view to starting window "
+ + "at taskId: " + taskId, e);
+ contentView = null;
+ }
+ }
+ record.setSplashScreenView(contentView);
+ }
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ };
+ if (mSysuiProxy != null) {
+ mSysuiProxy.requestTopUi(true, TAG);
+ }
+ mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
+ viewSupplier::setView, viewSupplier::setUiThreadInitTask);
+ try {
+ if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) {
+ // We use the splash screen worker thread to create SplashScreenView while adding
+ // the window, as otherwise Choreographer#doFrame might be delayed on this thread.
+ // And since Choreographer#doFrame won't happen immediately after adding the window,
+ // if the view is not added to the PhoneWindow on the first #doFrame, the view will
+ // not be rendered on the first frame. So here we need to synchronize the view on
+ // the window before first round relayoutWindow, which will happen after insets
+ // animation.
+ mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
+ final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+ record.parseAppSystemBarColor(context);
+ // Block until we get the background color.
+ final SplashScreenView contentView = viewSupplier.get();
+ if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
+ contentView.addOnAttachStateChangeListener(
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ final int lightBarAppearance = ContrastColorUtil.isColorLight(
+ contentView.getInitBackgroundColor())
+ ? LIGHT_BARS_MASK : 0;
+ contentView.getWindowInsetsController().setSystemBarsAppearance(
+ lightBarAppearance, LIGHT_BARS_MASK);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+ }
+ record.mBGColor = contentView.getInitBackgroundColor();
+ } else {
+ // release the icon view host
+ final SplashScreenView contentView = viewSupplier.get();
+ if (contentView.getSurfaceHost() != null) {
+ SplashScreenView.releaseIconHost(contentView.getSurfaceHost());
+ }
+ }
+ } catch (RuntimeException e) {
+ // don't crash if something else bad happens, for example a
+ // failure loading resources because we are loading from an app
+ // on external storage that has been unmounted.
+ Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e);
+ }
}
int getStartingWindowBackgroundColorForTask(int taskId) {
- final StartingWindowRecord startingWindowRecord = mWindowRecords.getRecord(taskId);
+ final StartingWindowRecord startingWindowRecord = mStartingWindowRecords.get(taskId);
if (startingWindowRecord == null) {
return Color.TRANSPARENT;
}
- return startingWindowRecord.getBGColor();
+ return startingWindowRecord.mBGColor;
+ }
+
+ private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
+ private SplashScreenView mView;
+ private boolean mIsViewSet;
+ private Runnable mUiThreadInitTask;
+ void setView(SplashScreenView view) {
+ synchronized (this) {
+ mView = view;
+ mIsViewSet = true;
+ notify();
+ }
+ }
+
+ void setUiThreadInitTask(Runnable initTask) {
+ synchronized (this) {
+ mUiThreadInitTask = initTask;
+ }
+ }
+
+ @Override
+ @Nullable
+ public SplashScreenView get() {
+ synchronized (this) {
+ while (!mIsViewSet) {
+ try {
+ wait();
+ } catch (InterruptedException ignored) {
+ }
+ }
+ if (mUiThreadInitTask != null) {
+ mUiThreadInitTask.run();
+ mUiThreadInitTask = null;
+ }
+ return mView;
+ }
+ }
}
int estimateTaskBackgroundColor(TaskInfo taskInfo) {
- return mSplashscreenWindowCreator.estimateTaskBackgroundColor(taskInfo);
+ if (taskInfo.topActivityInfo == null) {
+ return Color.TRANSPARENT;
+ }
+ final ActivityInfo activityInfo = taskInfo.topActivityInfo;
+ final String packageName = activityInfo.packageName;
+ final int userId = taskInfo.userId;
+ final Context windowContext;
+ try {
+ windowContext = mContext.createPackageContextAsUser(
+ packageName, Context.CONTEXT_RESTRICTED, UserHandle.of(userId));
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Failed creating package context with package name "
+ + packageName + " for user " + taskInfo.userId, e);
+ return Color.TRANSPARENT;
+ }
+ try {
+ final IPackageManager packageManager = ActivityThread.getPackageManager();
+ final String splashScreenThemeName = packageManager.getSplashScreenTheme(packageName,
+ userId);
+ final int splashScreenThemeId = splashScreenThemeName != null
+ ? windowContext.getResources().getIdentifier(splashScreenThemeName, null, null)
+ : 0;
+
+ final int theme = getSplashScreenTheme(splashScreenThemeId, activityInfo);
+
+ if (theme != windowContext.getThemeResId()) {
+ windowContext.setTheme(theme);
+ }
+ return mSplashscreenContentDrawer.estimateTaskBackgroundColor(windowContext);
+ } catch (RuntimeException | RemoteException e) {
+ Slog.w(TAG, "failed get starting window background color at taskId: "
+ + taskInfo.taskId, e);
+ }
+ return Color.TRANSPARENT;
}
/**
* Called when a task need a snapshot starting window.
*/
- void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) {
- mSnapshotWindowCreator.makeTaskSnapshotWindow(startingWindowInfo, snapshot);
+ void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, IBinder appToken,
+ TaskSnapshot snapshot) {
+ final int taskId = startingWindowInfo.taskInfo.taskId;
+ // Remove any existing starting window for this task before adding.
+ removeWindowNoAnimate(taskId);
+ final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken,
+ snapshot, mSplashScreenExecutor, () -> removeWindowNoAnimate(taskId));
+ if (surface == null) {
+ return;
+ }
+ final StartingWindowRecord tView = new StartingWindowRecord(appToken,
+ null/* decorView */, surface, STARTING_WINDOW_TYPE_SNAPSHOT);
+ mStartingWindowRecords.put(taskId, tView);
}
/**
* Called when the content of a task is ready to show, starting window can be removed.
*/
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
- if (removalInfo.windowlessSurface) {
- mWindowlessRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
- } else {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Task start finish, remove starting surface for task: %d",
- removalInfo.taskId);
- mWindowRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
- }
- }
-
- /**
- * Create a windowless starting surface and attach to the root surface.
- */
- void addWindowlessStartingSurface(StartingWindowInfo windowInfo) {
- if (windowInfo.taskSnapshot != null) {
- mWindowlessSnapshotWindowCreator.makeTaskSnapshotWindow(windowInfo,
- windowInfo.rootSurface, windowInfo.taskSnapshot, mSplashScreenExecutor);
- } else {
- mWindowlessSplashWindowCreator.addSplashScreenStartingWindow(
- windowInfo, windowInfo.rootSurface);
- }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Task start finish, remove starting surface for task: %d",
+ removalInfo.taskId);
+ removeWindowSynced(removalInfo, false /* immediately */);
}
/**
@@ -154,15 +419,37 @@
public void clearAllWindows() {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Clear all starting windows immediately");
- mWindowRecords.clearAllWindows();
- mWindowlessRecords.clearAllWindows();
+ final int taskSize = mStartingWindowRecords.size();
+ final int[] taskIds = new int[taskSize];
+ for (int i = taskSize - 1; i >= 0; --i) {
+ taskIds[i] = mStartingWindowRecords.keyAt(i);
+ }
+ for (int i = taskSize - 1; i >= 0; --i) {
+ removeWindowNoAnimate(taskIds[i]);
+ }
}
/**
* Called when the Task wants to copy the splash screen.
*/
public void copySplashScreenView(int taskId) {
- mSplashscreenWindowCreator.copySplashScreenView(taskId);
+ final StartingWindowRecord preView = mStartingWindowRecords.get(taskId);
+ SplashScreenViewParcelable parcelable;
+ SplashScreenView splashScreenView = preView != null ? preView.mContentView : null;
+ if (splashScreenView != null && splashScreenView.isCopyable()) {
+ parcelable = new SplashScreenViewParcelable(splashScreenView);
+ parcelable.setClientCallback(
+ new RemoteCallback((bundle) -> mSplashScreenExecutor.execute(
+ () -> onAppSplashScreenViewRemoved(taskId, false))));
+ splashScreenView.onCopied();
+ mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost());
+ } else {
+ parcelable = null;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Copying splash screen window view for task: %d with parcelable %b",
+ taskId, parcelable != null);
+ ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
}
/**
@@ -172,148 +459,195 @@
* @param taskId The Task id on which the splash screen was attached
*/
public void onAppSplashScreenViewRemoved(int taskId) {
- mSplashscreenWindowCreator.onAppSplashScreenViewRemoved(taskId);
+ onAppSplashScreenViewRemoved(taskId, true /* fromServer */);
+ }
+
+ /**
+ * @param fromServer If true, this means the removal was notified by the server. This is only
+ * used for debugging purposes.
+ * @see #onAppSplashScreenViewRemoved(int)
+ */
+ private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) {
+ SurfaceControlViewHost viewHost =
+ mAnimatedSplashScreenSurfaceHosts.get(taskId);
+ if (viewHost == null) {
+ return;
+ }
+ mAnimatedSplashScreenSurfaceHosts.remove(taskId);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "%s the splash screen. Releasing SurfaceControlViewHost for task: %d",
+ fromServer ? "Server cleaned up" : "App removed", taskId);
+ SplashScreenView.releaseIconHost(viewHost);
+ }
+
+ protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
+ WindowManager.LayoutParams params, @StartingWindowType int suggestType) {
+ boolean shouldSaveView = true;
+ final Context context = view.getContext();
+ try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
+ mWindowManagerGlobal.addView(view, params, display,
+ null /* parentWindow */, context.getUserId());
+ } catch (WindowManager.BadTokenException e) {
+ // ignore
+ Slog.w(TAG, appToken + " already running, starting window not displayed. "
+ + e.getMessage());
+ shouldSaveView = false;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ if (view.getParent() == null) {
+ Slog.w(TAG, "view not successfully added to wm, removing view");
+ mWindowManagerGlobal.removeView(view, true /* immediate */);
+ shouldSaveView = false;
+ }
+ }
+ if (shouldSaveView) {
+ removeWindowNoAnimate(taskId);
+ saveSplashScreenRecord(appToken, taskId, view, suggestType);
+ }
+ return shouldSaveView;
+ }
+
+ @VisibleForTesting
+ void saveSplashScreenRecord(IBinder appToken, int taskId, View view,
+ @StartingWindowType int suggestType) {
+ final StartingWindowRecord tView = new StartingWindowRecord(appToken, view,
+ null/* TaskSnapshotWindow */, suggestType);
+ mStartingWindowRecords.put(taskId, tView);
+ }
+
+ private void removeWindowNoAnimate(int taskId) {
+ mTmpRemovalInfo.taskId = taskId;
+ removeWindowSynced(mTmpRemovalInfo, true /* immediately */);
}
void onImeDrawnOnTask(int taskId) {
- onImeDrawnOnTask(mWindowRecords, taskId);
- onImeDrawnOnTask(mWindowlessRecords, taskId);
- }
-
- private void onImeDrawnOnTask(StartingWindowRecordManager records, int taskId) {
- final StartingSurfaceDrawer.StartingWindowRecord sRecord =
- records.getRecord(taskId);
- final SnapshotRecord record = sRecord instanceof SnapshotRecord
- ? (SnapshotRecord) sRecord : null;
- if (record != null && record.hasImeSurface()) {
- records.removeWindow(taskId, true);
+ final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+ if (record != null && record.mTaskSnapshotWindow != null
+ && record.mTaskSnapshotWindow.hasImeSurface()) {
+ removeWindowNoAnimate(taskId);
}
}
- static class WindowlessStartingWindow extends WindowlessWindowManager {
- SurfaceControl mChildSurface;
+ protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo, boolean immediately) {
+ final int taskId = removalInfo.taskId;
+ final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+ if (record != null) {
+ if (record.mDecorView != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Removing splash screen window for task: %d", taskId);
+ if (record.mContentView != null) {
+ record.clearSystemBarColor();
+ if (immediately
+ || record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
+ removeWindowInner(record.mDecorView, false);
+ } else {
+ if (removalInfo.playRevealAnimation) {
+ mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
+ removalInfo.windowAnimationLeash, removalInfo.mainFrame,
+ () -> removeWindowInner(record.mDecorView, true),
+ record.mCreateTime, removalInfo.roundedCornerRadius);
+ } else {
+ // the SplashScreenView has been copied to client, hide the view to skip
+ // default exit animation
+ removeWindowInner(record.mDecorView, true);
+ }
+ }
+ } else {
+ // shouldn't happen
+ Slog.e(TAG, "Found empty splash screen, remove!");
+ removeWindowInner(record.mDecorView, false);
+ }
- WindowlessStartingWindow(Configuration c, SurfaceControl rootSurface) {
- super(c, rootSurface, null /* hostInputToken */);
- }
-
- @Override
- protected SurfaceControl getParentSurface(IWindow window,
- WindowManager.LayoutParams attrs) {
- final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
- .setContainerLayer()
- .setName("Windowless window")
- .setHidden(false)
- .setParent(mRootSurface)
- .setCallsite("WindowlessStartingWindow#attachToParentSurface");
- mChildSurface = builder.build();
- try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
- t.setLayer(mChildSurface, Integer.MAX_VALUE);
- t.apply();
}
- return mChildSurface;
- }
- }
- abstract static class StartingWindowRecord {
- protected int mBGColor;
- abstract void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately);
- int getBGColor() {
- return mBGColor;
- }
- }
-
- abstract static class SnapshotRecord extends StartingWindowRecord {
- private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
- /**
- * The max delay time in milliseconds for removing the task snapshot window with IME
- * visible.
- * Ideally the delay time will be shorter when receiving
- * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
- */
- private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
- private final Runnable mScheduledRunnable = this::removeImmediately;
-
- @WindowConfiguration.ActivityType protected final int mActivityType;
- protected final ShellExecutor mRemoveExecutor;
-
- SnapshotRecord(int activityType, ShellExecutor removeExecutor) {
- mActivityType = activityType;
- mRemoveExecutor = removeExecutor;
- }
-
- @Override
- public final void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
- if (immediately) {
- removeImmediately();
- } else {
- scheduleRemove(info.deferRemoveForIme);
+ if (record.mTaskSnapshotWindow != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Removing task snapshot window for %d", taskId);
+ if (immediately) {
+ record.mTaskSnapshotWindow.removeImmediately();
+ } else {
+ record.mTaskSnapshotWindow.scheduleRemove(removalInfo.deferRemoveForIme);
+ }
}
+ mStartingWindowRecords.remove(taskId);
+ }
+ }
+
+ private void removeWindowInner(View decorView, boolean hideView) {
+ if (mSysuiProxy != null) {
+ mSysuiProxy.requestTopUi(false, TAG);
+ }
+ if (hideView) {
+ decorView.setVisibility(View.GONE);
+ }
+ mWindowManagerGlobal.removeView(decorView, false /* immediate */);
+ }
+
+ /**
+ * Record the view or surface for a starting window.
+ */
+ private static class StartingWindowRecord {
+ private final IBinder mAppToken;
+ private final View mDecorView;
+ private final TaskSnapshotWindow mTaskSnapshotWindow;
+ private SplashScreenView mContentView;
+ private boolean mSetSplashScreen;
+ @StartingWindowType private int mSuggestType;
+ private int mBGColor;
+ private final long mCreateTime;
+ private int mSystemBarAppearance;
+ private boolean mDrawsSystemBarBackgrounds;
+
+ StartingWindowRecord(IBinder appToken, View decorView,
+ TaskSnapshotWindow taskSnapshotWindow, @StartingWindowType int suggestType) {
+ mAppToken = appToken;
+ mDecorView = decorView;
+ mTaskSnapshotWindow = taskSnapshotWindow;
+ if (mTaskSnapshotWindow != null) {
+ mBGColor = mTaskSnapshotWindow.getBackgroundColor();
+ }
+ mSuggestType = suggestType;
+ mCreateTime = SystemClock.uptimeMillis();
}
- void scheduleRemove(boolean deferRemoveForIme) {
- // Show the latest content as soon as possible for unlocking to home.
- if (mActivityType == ACTIVITY_TYPE_HOME) {
- removeImmediately();
+ private void setSplashScreenView(SplashScreenView splashScreenView) {
+ if (mSetSplashScreen) {
return;
}
- mRemoveExecutor.removeCallbacks(mScheduledRunnable);
- final long delayRemovalTime = hasImeSurface() && deferRemoveForIme
- ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
- : DELAY_REMOVAL_TIME_GENERAL;
- mRemoveExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Defer removing snapshot surface in %d", delayRemovalTime);
+ mContentView = splashScreenView;
+ mSetSplashScreen = true;
}
- protected abstract boolean hasImeSurface();
-
- @CallSuper
- protected void removeImmediately() {
- mRemoveExecutor.removeCallbacks(mScheduledRunnable);
- }
- }
-
- static class StartingWindowRecordManager {
- private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
- private final SparseArray<StartingWindowRecord> mStartingWindowRecords =
- new SparseArray<>();
-
- void clearAllWindows() {
- final int taskSize = mStartingWindowRecords.size();
- final int[] taskIds = new int[taskSize];
- for (int i = taskSize - 1; i >= 0; --i) {
- taskIds[i] = mStartingWindowRecords.keyAt(i);
+ private void parseAppSystemBarColor(Context context) {
+ final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
+ mDrawsSystemBarBackgrounds = a.getBoolean(
+ R.styleable.Window_windowDrawsSystemBarBackgrounds, false);
+ if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
+ mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
}
- for (int i = taskSize - 1; i >= 0; --i) {
- removeWindow(taskIds[i], true);
+ if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
+ mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
}
+ a.recycle();
}
- void addRecord(int taskId, StartingWindowRecord record) {
- mStartingWindowRecords.put(taskId, record);
- }
-
- void removeWindow(StartingWindowRemovalInfo removeInfo, boolean immediately) {
- final int taskId = removeInfo.taskId;
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- if (record != null) {
- record.removeIfPossible(removeInfo, immediately);
- mStartingWindowRecords.remove(taskId);
+ // Reset the system bar color which set by splash screen, make it align to the app.
+ private void clearSystemBarColor() {
+ if (mDecorView == null || !mDecorView.isAttachedToWindow()) {
+ return;
}
- }
-
- void removeWindow(int taskId, boolean immediately) {
- mTmpRemovalInfo.taskId = taskId;
- removeWindow(mTmpRemovalInfo, immediately);
- }
-
- StartingWindowRecord getRecord(int taskId) {
- return mStartingWindowRecords.get(taskId);
- }
-
- @VisibleForTesting
- int recordSize() {
- return mStartingWindowRecords.size();
+ if (mDecorView.getLayoutParams() instanceof WindowManager.LayoutParams) {
+ final WindowManager.LayoutParams lp =
+ (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+ if (mDrawsSystemBarBackgrounds) {
+ lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ } else {
+ lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ }
+ mDecorView.setLayoutParams(lp);
+ }
+ mDecorView.getWindowInsetsController().setSystemBarsAppearance(
+ mSystemBarAppearance, LIGHT_BARS_MASK);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index bec4ba3..be2e793 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -21,7 +21,6 @@
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
@@ -30,6 +29,7 @@
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Color;
+import android.os.IBinder;
import android.os.Trace;
import android.util.SparseIntArray;
import android.window.StartingWindowInfo;
@@ -152,23 +152,22 @@
/**
* Called when a task need a starting window.
*/
- public void addStartingWindow(StartingWindowInfo windowInfo) {
+ public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
mSplashScreenExecutor.execute(() -> {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow");
final int suggestionType = mStartingWindowTypeAlgorithm.getSuggestedWindowType(
windowInfo);
final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
- if (suggestionType == STARTING_WINDOW_TYPE_WINDOWLESS) {
- mStartingSurfaceDrawer.addWindowlessStartingSurface(windowInfo);
- } else if (isSplashScreenType(suggestionType)) {
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, suggestionType);
+ if (isSplashScreenType(suggestionType)) {
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken,
+ suggestionType);
} else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
final TaskSnapshot snapshot = windowInfo.taskSnapshot;
- mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, snapshot);
+ mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken,
+ snapshot);
}
- if (suggestionType != STARTING_WINDOW_TYPE_NONE
- && suggestionType != STARTING_WINDOW_TYPE_WINDOWLESS) {
+ if (suggestionType != STARTING_WINDOW_TYPE_NONE) {
int taskId = runningTaskInfo.taskId;
int color = mStartingSurfaceDrawer
.getStartingWindowBackgroundColorForTask(taskId);
@@ -219,13 +218,11 @@
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.removeStartingWindow(
removalInfo));
- if (!removalInfo.windowlessSurface) {
- mSplashScreenExecutor.executeDelayed(() -> {
- synchronized (mTaskBackgroundColors) {
- mTaskBackgroundColors.delete(removalInfo.taskId);
- }
- }, TASK_BG_COLOR_RETAIN_TIME_MS);
- }
+ mSplashScreenExecutor.executeDelayed(() -> {
+ synchronized (mTaskBackgroundColors) {
+ mTaskBackgroundColors.delete(removalInfo.taskId);
+ }
+ }, TASK_BG_COLOR_RETAIN_TIME_MS);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index c964df1..a05ed4f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.startingsurface;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.graphics.Color.WHITE;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -62,14 +63,24 @@
private static final String TAG = StartingWindowController.TAG;
private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=";
+ private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
+ /**
+ * The max delay time in milliseconds for removing the task snapshot window with IME visible.
+ * Ideally the delay time will be shorter when receiving
+ * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
+ */
+ private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
+
private final Window mWindow;
private final Runnable mClearWindowHandler;
private final ShellExecutor mSplashScreenExecutor;
private final IWindowSession mSession;
private boolean mHasDrawn;
private final Paint mBackgroundPaint = new Paint();
+ private final int mActivityType;
private final int mOrientationOnCreation;
+ private final Runnable mScheduledRunnable = this::removeImmediately;
private final boolean mHasImeSurface;
static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken,
@@ -93,6 +104,7 @@
final Point taskSize = snapshot.getTaskSize();
final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y);
final int orientation = snapshot.getOrientation();
+ final int activityType = runningTaskInfo.topActivityType;
final int displayId = runningTaskInfo.displayId;
final IWindowSession session = WindowManagerGlobal.getWindowSession();
@@ -102,11 +114,16 @@
final InsetsSourceControl.Array tmpControls = new InsetsSourceControl.Array();
final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
- final TaskDescription taskDescription =
- SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo);
+ final TaskDescription taskDescription;
+ if (runningTaskInfo.taskDescription != null) {
+ taskDescription = runningTaskInfo.taskDescription;
+ } else {
+ taskDescription = new TaskDescription();
+ taskDescription.setBackgroundColor(WHITE);
+ }
final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
- snapshot, taskDescription, orientation,
+ snapshot, taskDescription, orientation, activityType,
clearWindowHandler, splashScreenExecutor);
final Window window = snapshotSurface.mWindow;
@@ -136,8 +153,6 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} catch (RemoteException e) {
snapshotSurface.clearWindowSynced();
- Slog.w(TAG, "Failed to relayout snapshot starting window");
- return null;
}
SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot,
@@ -149,7 +164,7 @@
}
public TaskSnapshotWindow(TaskSnapshot snapshot, TaskDescription taskDescription,
- int currentOrientation, Runnable clearWindowHandler,
+ int currentOrientation, int activityType, Runnable clearWindowHandler,
ShellExecutor splashScreenExecutor) {
mSplashScreenExecutor = splashScreenExecutor;
mSession = WindowManagerGlobal.getWindowSession();
@@ -158,6 +173,7 @@
int backgroundColor = taskDescription.getBackgroundColor();
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
mOrientationOnCreation = currentOrientation;
+ mActivityType = activityType;
mClearWindowHandler = clearWindowHandler;
mHasImeSurface = snapshot.hasImeSurface();
}
@@ -170,7 +186,23 @@
return mHasImeSurface;
}
+ void scheduleRemove(boolean deferRemoveForIme) {
+ // Show the latest content as soon as possible for unlocking to home.
+ if (mActivityType == ACTIVITY_TYPE_HOME) {
+ removeImmediately();
+ return;
+ }
+ mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
+ final long delayRemovalTime = mHasImeSurface && deferRemoveForIme
+ ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
+ : DELAY_REMOVAL_TIME_GENERAL;
+ mSplashScreenExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "Defer removing snapshot surface in %d", delayRemovalTime);
+ }
+
void removeImmediately() {
+ mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Removing taskSnapshot surface, mHasDrawn=%b", mHasDrawn);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
deleted file mode 100644
index 1445478..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.startingsurface;
-
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
-import android.view.Display;
-import android.view.InsetsState;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-import android.window.SnapshotDrawerUtils;
-import android.window.StartingWindowInfo;
-import android.window.TaskSnapshot;
-
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
-
-class WindowlessSnapshotWindowCreator {
- private static final int DEFAULT_FADEOUT_DURATION = 233;
- private final StartingSurfaceDrawer.StartingWindowRecordManager
- mStartingWindowRecordManager;
- private final DisplayManager mDisplayManager;
- private final Context mContext;
- private final SplashscreenContentDrawer mSplashscreenContentDrawer;
- private final TransactionPool mTransactionPool;
-
- WindowlessSnapshotWindowCreator(
- StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager,
- Context context,
- DisplayManager displayManager, SplashscreenContentDrawer splashscreenContentDrawer,
- TransactionPool transactionPool) {
- mStartingWindowRecordManager = startingWindowRecordManager;
- mContext = context;
- mDisplayManager = displayManager;
- mSplashscreenContentDrawer = splashscreenContentDrawer;
- mTransactionPool = transactionPool;
- }
-
- void makeTaskSnapshotWindow(StartingWindowInfo info, SurfaceControl rootSurface,
- TaskSnapshot snapshot, ShellExecutor removeExecutor) {
- final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
- final int taskId = runningTaskInfo.taskId;
- final String title = "Windowless Snapshot " + taskId;
- final WindowManager.LayoutParams lp = SnapshotDrawerUtils.createLayoutParameters(
- info, title, TYPE_APPLICATION_OVERLAY, snapshot.getHardwareBuffer().getFormat(),
- null /* token */);
- if (lp == null) {
- return;
- }
- final Display display = mDisplayManager.getDisplay(runningTaskInfo.displayId);
- final StartingSurfaceDrawer.WindowlessStartingWindow wlw =
- new StartingSurfaceDrawer.WindowlessStartingWindow(
- runningTaskInfo.configuration, rootSurface);
- final SurfaceControlViewHost mViewHost = new SurfaceControlViewHost(
- mContext, display, wlw, "WindowlessSnapshotWindowCreator");
- final Point taskSize = snapshot.getTaskSize();
- final Rect snapshotBounds = new Rect(0, 0, taskSize.x, taskSize.y);
- final Rect windowBounds = runningTaskInfo.configuration.windowConfiguration.getBounds();
- final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
- final FrameLayout rootLayout = new FrameLayout(
- mSplashscreenContentDrawer.createViewContextWrapper(mContext));
- mViewHost.setView(rootLayout, lp);
- SnapshotDrawerUtils.drawSnapshotOnSurface(info, lp, wlw.mChildSurface, snapshot,
- snapshotBounds, windowBounds, topWindowInsetsState, false /* releaseAfterDraw */);
-
- final ActivityManager.TaskDescription taskDescription =
- SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo);
-
- final SnapshotWindowRecord record = new SnapshotWindowRecord(mViewHost, wlw.mChildSurface,
- taskDescription.getBackgroundColor(), snapshot.hasImeSurface(),
- runningTaskInfo.topActivityType, removeExecutor);
- mStartingWindowRecordManager.addRecord(taskId, record);
- info.notifyAddComplete(wlw.mChildSurface);
- }
-
- private class SnapshotWindowRecord extends StartingSurfaceDrawer.SnapshotRecord {
- private SurfaceControlViewHost mViewHost;
- private SurfaceControl mChildSurface;
- private final boolean mHasImeSurface;
-
- SnapshotWindowRecord(SurfaceControlViewHost viewHost, SurfaceControl childSurface,
- int bgColor, boolean hasImeSurface, int activityType,
- ShellExecutor removeExecutor) {
- super(activityType, removeExecutor);
- mViewHost = viewHost;
- mChildSurface = childSurface;
- mBGColor = bgColor;
- mHasImeSurface = hasImeSurface;
- }
-
- @Override
- protected void removeImmediately() {
- super.removeImmediately();
- fadeoutThenRelease();
- }
-
- void fadeoutThenRelease() {
- final ValueAnimator fadeOutAnimator = ValueAnimator.ofFloat(1f, 0f);
- fadeOutAnimator.setDuration(DEFAULT_FADEOUT_DURATION);
- final SurfaceControl.Transaction t = mTransactionPool.acquire();
- fadeOutAnimator.addUpdateListener(animation -> {
- if (mChildSurface == null || !mChildSurface.isValid()) {
- fadeOutAnimator.cancel();
- return;
- }
- t.setAlpha(mChildSurface, (float) animation.getAnimatedValue());
- t.apply();
- });
-
- fadeOutAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- if (mChildSurface == null || !mChildSurface.isValid()) {
- fadeOutAnimator.cancel();
- }
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mTransactionPool.release(t);
- if (mChildSurface != null) {
- final SurfaceControl.Transaction t = mTransactionPool.acquire();
- t.remove(mChildSurface).apply();
- mTransactionPool.release(t);
- mChildSurface = null;
- }
- if (mViewHost != null) {
- mViewHost.release();
- mViewHost = null;
- }
- }
- });
- fadeOutAnimator.start();
- }
-
- @Override
- protected boolean hasImeSurface() {
- return mHasImeSurface;
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
deleted file mode 100644
index 12a0d40..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.startingsurface;
-
-import static android.graphics.Color.WHITE;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
-import android.os.Binder;
-import android.os.SystemClock;
-import android.view.Display;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-import android.window.SplashScreenView;
-import android.window.StartingWindowInfo;
-import android.window.StartingWindowRemovalInfo;
-
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
-
-class WindowlessSplashWindowCreator extends AbsSplashWindowCreator {
-
- private final TransactionPool mTransactionPool;
-
- WindowlessSplashWindowCreator(SplashscreenContentDrawer contentDrawer,
- Context context,
- ShellExecutor splashScreenExecutor,
- DisplayManager displayManager,
- StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager,
- TransactionPool pool) {
- super(contentDrawer, context, splashScreenExecutor, displayManager,
- startingWindowRecordManager);
- mTransactionPool = pool;
- }
-
- void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, SurfaceControl rootSurface) {
- final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
- final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
- ? windowInfo.targetActivityInfo
- : taskInfo.topActivityInfo;
- if (activityInfo == null || activityInfo.packageName == null) {
- return;
- }
-
- final int displayId = taskInfo.displayId;
- final Display display = mDisplayManager.getDisplay(displayId);
- if (display == null) {
- // Can't show splash screen on requested display, so skip showing at all.
- return;
- }
- final Context myContext = SplashscreenContentDrawer.createContext(mContext, windowInfo,
- 0 /* theme */, STARTING_WINDOW_TYPE_SPLASH_SCREEN, mDisplayManager);
- if (myContext == null) {
- return;
- }
- final StartingSurfaceDrawer.WindowlessStartingWindow wlw =
- new StartingSurfaceDrawer.WindowlessStartingWindow(
- taskInfo.configuration, rootSurface);
- final SurfaceControlViewHost viewHost = new SurfaceControlViewHost(
- myContext, display, wlw, "WindowlessSplashWindowCreator");
- final String title = "Windowless Splash " + taskInfo.taskId;
- final WindowManager.LayoutParams lp = SplashscreenContentDrawer.createLayoutParameters(
- myContext, windowInfo, STARTING_WINDOW_TYPE_SPLASH_SCREEN, title,
- PixelFormat.TRANSLUCENT, new Binder());
- final Rect windowBounds = taskInfo.configuration.windowConfiguration.getBounds();
- lp.width = windowBounds.width();
- lp.height = windowBounds.height();
- final ActivityManager.TaskDescription taskDescription;
- if (taskInfo.taskDescription != null) {
- taskDescription = taskInfo.taskDescription;
- } else {
- taskDescription = new ActivityManager.TaskDescription();
- taskDescription.setBackgroundColor(WHITE);
- }
-
- final FrameLayout rootLayout = new FrameLayout(
- mSplashscreenContentDrawer.createViewContextWrapper(mContext));
- viewHost.setView(rootLayout, lp);
-
- final int bgColor = taskDescription.getBackgroundColor();
- final SplashScreenView splashScreenView = mSplashscreenContentDrawer
- .makeSimpleSplashScreenContentView(myContext, windowInfo, bgColor);
- rootLayout.addView(splashScreenView);
- final SplashWindowRecord record = new SplashWindowRecord(viewHost, splashScreenView,
- wlw.mChildSurface, bgColor);
- mStartingWindowRecordManager.addRecord(taskInfo.taskId, record);
- windowInfo.notifyAddComplete(wlw.mChildSurface);
- }
-
- private class SplashWindowRecord extends StartingSurfaceDrawer.StartingWindowRecord {
- private SurfaceControlViewHost mViewHost;
- private final long mCreateTime;
- private SurfaceControl mChildSurface;
- private final SplashScreenView mSplashView;
-
- SplashWindowRecord(SurfaceControlViewHost viewHost, SplashScreenView splashView,
- SurfaceControl childSurface, int bgColor) {
- mViewHost = viewHost;
- mSplashView = splashView;
- mChildSurface = childSurface;
- mBGColor = bgColor;
- mCreateTime = SystemClock.uptimeMillis();
- }
-
- @Override
- public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
- if (!immediately) {
- mSplashscreenContentDrawer.applyExitAnimation(mSplashView,
- info.windowAnimationLeash, info.mainFrame,
- this::release, mCreateTime, 0 /* roundedCornerRadius */);
- } else {
- release();
- }
- }
-
- void release() {
- if (mChildSurface != null) {
- final SurfaceControl.Transaction t = mTransactionPool.acquire();
- t.remove(mChildSurface).apply();
- mTransactionPool.release(t);
- mChildSurface = null;
- }
- if (mViewHost != null) {
- mViewHost.release();
- mViewHost = null;
- }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
index 72fc8686..bb43d7c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
@@ -22,7 +22,6 @@
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_DRAWN;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
@@ -31,7 +30,6 @@
import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN;
-import static android.window.StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS;
import android.window.StartingWindowInfo;
@@ -57,7 +55,6 @@
final boolean legacySplashScreen =
((parameter & TYPE_PARAMETER_LEGACY_SPLASH_SCREEN) != 0);
final boolean activityDrawn = (parameter & TYPE_PARAMETER_ACTIVITY_DRAWN) != 0;
- final boolean windowlessSurface = (parameter & TYPE_PARAMETER_WINDOWLESS) != 0;
final boolean topIsHome = windowInfo.taskInfo.topActivityType == ACTIVITY_TYPE_HOME;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
@@ -70,15 +67,10 @@
+ "isSolidColorSplashScreen=%b, "
+ "legacySplashScreen=%b, "
+ "activityDrawn=%b, "
- + "windowless=%b, "
+ "topIsHome=%b",
newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated,
- isSolidColorSplashScreen, legacySplashScreen, activityDrawn, windowlessSurface,
- topIsHome);
+ isSolidColorSplashScreen, legacySplashScreen, activityDrawn, topIsHome);
- if (windowlessSurface) {
- return STARTING_WINDOW_TYPE_WINDOWLESS;
- }
if (!topIsHome) {
if (!processRunning || newTask || (taskSwitch && !activityCreated)) {
return getSplashscreenType(isSolidColorSplashScreen, legacySplashScreen);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index bf62acf..11fda8b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -24,8 +24,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.wm.shell.startingsurface.SplashscreenContentDrawer.MAX_ANIMATION_DURATION;
-import static com.android.wm.shell.startingsurface.SplashscreenContentDrawer.MINIMAL_ANIMATION_DURATION;
+import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MAX_ANIMATION_DURATION;
+import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MINIMAL_ANIMATION_DURATION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -56,9 +56,11 @@
import android.os.Looper;
import android.os.UserHandle;
import android.testing.TestableContext;
+import android.view.Display;
import android.view.IWindowSession;
import android.view.InsetsState;
import android.view.Surface;
+import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowMetrics;
@@ -104,7 +106,36 @@
private ShellExecutor mTestExecutor;
private final TestableContext mTestContext = new TestContext(
InstrumentationRegistry.getInstrumentation().getTargetContext());
- StartingSurfaceDrawer mStartingSurfaceDrawer;
+ TestStartingSurfaceDrawer mStartingSurfaceDrawer;
+
+ static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{
+ int mAddWindowForTask = 0;
+
+ TestStartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
+ IconProvider iconProvider, TransactionPool pool) {
+ super(context, splashScreenExecutor, iconProvider, pool);
+ }
+
+ @Override
+ protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
+ WindowManager.LayoutParams params, int suggestType) {
+ // listen for addView
+ mAddWindowForTask = taskId;
+ saveSplashScreenRecord(appToken, taskId, view, suggestType);
+ // Do not wait for background color
+ return false;
+ }
+
+ @Override
+ protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo,
+ boolean immediately) {
+ // listen for removeView
+ if (mAddWindowForTask == removalInfo.taskId) {
+ mAddWindowForTask = 0;
+ }
+ mStartingWindowRecords.remove(removalInfo.taskId);
+ }
+ }
private static class TestContext extends TestableContext {
TestContext(Context context) {
@@ -134,51 +165,44 @@
doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics();
doNothing().when(mMockWindowManager).addView(any(), any());
mTestExecutor = new HandlerExecutor(mTestHandler);
- mStartingSurfaceDrawer = new StartingSurfaceDrawer(mTestContext, mTestExecutor,
- mIconProvider, mTransactionPool);
mStartingSurfaceDrawer = spy(
- new StartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider,
+ new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider,
mTransactionPool));
- spyOn(mStartingSurfaceDrawer.mSplashscreenWindowCreator);
- spyOn(mStartingSurfaceDrawer.mWindowRecords);
- spyOn(mStartingSurfaceDrawer.mWindowlessRecords);
}
@Test
public void testAddSplashScreenSurface() {
final int taskId = 1;
final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, android.R.style.Theme, mBinder);
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo,
+ createWindowInfo(taskId, android.R.style.Theme);
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
STARTING_WINDOW_TYPE_SPLASH_SCREEN);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer.mSplashscreenWindowCreator).addWindow(
- eq(taskId), eq(mBinder), any(), any(), any(),
+ verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(),
eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN));
+ assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
removalInfo.taskId = windowInfo.taskInfo.taskId;
mStartingSurfaceDrawer.removeStartingWindow(removalInfo);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer.mWindowRecords).removeWindow(any(), eq(false));
- assertEquals(mStartingSurfaceDrawer.mWindowRecords.recordSize(), 0);
+ verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(false));
+ assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0);
}
@Test
public void testFallbackDefaultTheme() {
final int taskId = 1;
final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, 0, mBinder);
+ createWindowInfo(taskId, 0);
final int[] theme = new int[1];
doAnswer(invocation -> theme[0] = (Integer) invocation.callRealMethod())
- .when(mStartingSurfaceDrawer.mSplashscreenWindowCreator)
- .getSplashScreenTheme(eq(0), any());
+ .when(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any());
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo,
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
STARTING_WINDOW_TYPE_SPLASH_SCREEN);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer.mSplashscreenWindowCreator)
- .getSplashScreenTheme(eq(0), any());
+ verify(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any());
assertNotEquals(theme[0], 0);
}
@@ -217,7 +241,7 @@
public void testRemoveTaskSnapshotWithImeSurfaceWhenOnImeDrawn() throws Exception {
final int taskId = 1;
final StartingWindowInfo windowInfo =
- createWindowInfo(taskId, android.R.style.Theme, mBinder);
+ createWindowInfo(taskId, android.R.style.Theme);
TaskSnapshot snapshot = createTaskSnapshot(100, 100, new Point(100, 100),
new Rect(0, 0, 0, 50), true /* hasImeSurface */);
final IWindowSession session = WindowManagerGlobal.getWindowSession();
@@ -246,7 +270,7 @@
when(TaskSnapshotWindow.create(eq(windowInfo), eq(mBinder), eq(snapshot), any(),
any())).thenReturn(mockSnapshotWindow);
// Simulate a task snapshot window created with IME snapshot shown.
- mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, snapshot);
+ mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, mBinder, snapshot);
waitHandlerIdle(mTestHandler);
// Verify the task snapshot with IME snapshot will be removed when received the real IME
@@ -254,36 +278,27 @@
// makeTaskSnapshotWindow shall call removeWindowSynced before there add a new
// StartingWindowRecord for the task.
mStartingSurfaceDrawer.onImeDrawnOnTask(1);
- verify(mStartingSurfaceDrawer.mWindowRecords, times(2))
- .removeWindow(any(), eq(true));
+ verify(mStartingSurfaceDrawer, times(2))
+ .removeWindowSynced(any(), eq(true));
}
}
@Test
public void testClearAllWindows() {
final int taskId = 1;
- mStartingSurfaceDrawer.mWindowRecords.addRecord(taskId,
- new StartingSurfaceDrawer.StartingWindowRecord() {
- @Override
- public void removeIfPossible(StartingWindowRemovalInfo info,
- boolean immediately) {
+ final StartingWindowInfo windowInfo =
+ createWindowInfo(taskId, android.R.style.Theme);
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
+ STARTING_WINDOW_TYPE_SPLASH_SCREEN);
+ waitHandlerIdle(mTestHandler);
+ verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(),
+ eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN));
+ assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
- }
- });
- mStartingSurfaceDrawer.mWindowlessRecords.addRecord(taskId,
- new StartingSurfaceDrawer.StartingWindowRecord() {
- @Override
- public void removeIfPossible(StartingWindowRemovalInfo info,
- boolean immediately) {
-
- }
- });
mStartingSurfaceDrawer.clearAllWindows();
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer.mWindowRecords).removeWindow(any(), eq(true));
- assertEquals(mStartingSurfaceDrawer.mWindowRecords.recordSize(), 0);
- verify(mStartingSurfaceDrawer.mWindowlessRecords).removeWindow(any(), eq(true));
- assertEquals(mStartingSurfaceDrawer.mWindowlessRecords.recordSize(), 0);
+ verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(true));
+ assertEquals(mStartingSurfaceDrawer.mStartingWindowRecords.size(), 0);
}
@Test
@@ -336,7 +351,7 @@
longAppDuration, longAppDuration));
}
- private StartingWindowInfo createWindowInfo(int taskId, int themeResId, IBinder appToken) {
+ private StartingWindowInfo createWindowInfo(int taskId, int themeResId) {
StartingWindowInfo windowInfo = new StartingWindowInfo();
final ActivityInfo info = new ActivityInfo();
info.applicationInfo = new ApplicationInfo();
@@ -345,7 +360,6 @@
final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.topActivityInfo = info;
taskInfo.taskId = taskId;
- windowInfo.appToken = appToken;
windowInfo.targetActivityInfo = info;
windowInfo.taskInfo = taskInfo;
windowInfo.topOpaqueWindowInsetsState = new InsetsState();
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 9f27f72..3fffdbe 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -23,6 +23,7 @@
import static android.content.ComponentName.createRelative;
import static com.android.server.companion.Utils.prepareForIpc;
+import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -91,7 +92,8 @@
mAssociationStore = associationStore;
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
mTransportManager = transportManager;
- mTransportManager.setListener(this::onReceivePermissionRestore);
+ mTransportManager.addListener(MESSAGE_REQUEST_PERMISSION_RESTORE,
+ this::onReceivePermissionRestore);
mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
mExecutor = Executors.newSingleThreadExecutor();
}
diff --git a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
index adaee75..1559a3f 100644
--- a/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
+++ b/services/companion/java/com/android/server/companion/securechannel/AttestationVerifier.java
@@ -35,7 +35,7 @@
/**
* Helper class to perform attestation verification synchronously.
*/
-class AttestationVerifier {
+public class AttestationVerifier {
private static final long ATTESTATION_VERIFICATION_TIMEOUT_SECONDS = 10; // 10 seconds
private static final String PARAM_OWNED_BY_SYSTEM = "android.key_owned_by_system";
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 13dba84..05b6022 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -110,7 +110,7 @@
this(in, out, callback, null, new AttestationVerifier(context));
}
- private SecureChannel(
+ public SecureChannel(
final InputStream in,
final OutputStream out,
Callback callback,
@@ -381,9 +381,10 @@
private void exchangeAuthentication()
throws IOException, GeneralSecurityException, BadHandleException, CryptoException {
- if (mVerifier == null) {
+ if (mPreSharedKey != null) {
exchangePreSharedKey();
- } else {
+ }
+ if (mVerifier != null) {
exchangeAttestation();
}
}
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 6a53adf..2abdcb1 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -19,9 +19,9 @@
import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE;
+import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PLATFORM_INFO;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManagerInternal;
import android.content.Context;
@@ -30,12 +30,17 @@
import android.os.Binder;
import android.os.Build;
import android.os.ParcelFileDescriptor;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
+import com.android.server.companion.transport.Transport.Listener;
import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
@@ -44,6 +49,8 @@
private static final String TAG = "CDM_CompanionTransportManager";
private static final boolean DEBUG = false;
+ private static final int NON_ANDROID = -1;
+
private boolean mSecureTransportEnabled = true;
private static boolean isRequest(int message) {
@@ -54,24 +61,29 @@
return (message & 0xFF000000) == 0x33000000;
}
- public interface Listener {
- void onRequestPermissionRestore(byte[] data);
- }
-
private final Context mContext;
@GuardedBy("mTransports")
private final SparseArray<Transport> mTransports = new SparseArray<>();
- @Nullable
- private Listener mListener;
+ @NonNull
+ private final Map<Integer, Listener> mListeners = new HashMap<>();
+
+ private Transport mTempTransport;
public CompanionTransportManager(Context context) {
mContext = context;
}
- public void setListener(@NonNull Listener listener) {
- mListener = listener;
+ /**
+ * Add a message listener when a message is received for the message type
+ */
+ @GuardedBy("mTransports")
+ public void addListener(int message, @NonNull Listener listener) {
+ mListeners.put(message, listener);
+ for (int i = 0; i < mTransports.size(); i++) {
+ mTransports.valueAt(i).addListener(message, listener);
+ }
}
/**
@@ -105,15 +117,7 @@
detachSystemDataTransport(packageName, userId, associationId);
}
- final Transport transport;
- if (isSecureTransportEnabled(associationId)) {
- transport = new SecureTransport(associationId, fd, mContext, mListener);
- } else {
- transport = new RawTransport(associationId, fd, mContext, mListener);
- }
-
- transport.start();
- mTransports.put(associationId, transport);
+ initializeTransport(associationId, fd);
}
}
@@ -128,13 +132,85 @@
}
}
+ @GuardedBy("mTransports")
+ private void initializeTransport(int associationId, ParcelFileDescriptor fd) {
+ if (!isSecureTransportEnabled()) {
+ Transport transport = new RawTransport(associationId, fd, mContext);
+ for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) {
+ transport.addListener(entry.getKey(), entry.getValue());
+ }
+ transport.start();
+ mTransports.put(associationId, transport);
+ Slog.i(TAG, "RawTransport is created");
+ return;
+ }
+
+ // Exchange platform info to decide which transport should be created
+ mTempTransport = new RawTransport(associationId, fd, mContext);
+ for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) {
+ mTempTransport.addListener(entry.getKey(), entry.getValue());
+ }
+ mTempTransport.addListener(MESSAGE_REQUEST_PLATFORM_INFO, this::onPlatformInfoReceived);
+ mTempTransport.start();
+
+ int sdk = Build.VERSION.SDK_INT;
+ String release = Build.VERSION.RELEASE;
+ // data format: | SDK_INT (int) | release length (int) | release |
+ final ByteBuffer data = ByteBuffer.allocate(4 + 4 + release.getBytes().length)
+ .putInt(sdk)
+ .putInt(release.getBytes().length)
+ .put(release.getBytes());
+
+ // TODO: it should check if preSharedKey is given
+ mTempTransport.requestForResponse(MESSAGE_REQUEST_PLATFORM_INFO, data.array());
+ }
+
+ /**
+ * Depending on the remote platform info to decide which transport should be created
+ */
+ @GuardedBy("mTransports")
+ private void onPlatformInfoReceived(byte[] data) {
+ // TODO: it should check if preSharedKey is given
+
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+ int remoteSdk = buffer.getInt();
+ byte[] remoteRelease = new byte[buffer.getInt()];
+ buffer.get(remoteRelease);
+
+ Slog.i(TAG, "Remote device SDK: " + remoteSdk + ", release:" + new String(remoteRelease));
+
+ Transport transport = mTempTransport;
+ mTempTransport = null;
+
+ int sdk = Build.VERSION.SDK_INT;
+ String release = Build.VERSION.RELEASE;
+ if (remoteSdk == NON_ANDROID) {
+ // TODO: pass in a real preSharedKey
+ transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
+ mContext, null, null);
+ } else if (sdk < Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+ || remoteSdk < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ // TODO: depending on the release version, either
+ // 1) using a RawTransport for old T versions
+ // 2) or an Ukey2 handshaked transport for UKey2 backported T versions
+ } else {
+ Slog.i(TAG, "Creating a secure channel");
+ transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
+ mContext);
+ for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) {
+ transport.addListener(entry.getKey(), entry.getValue());
+ }
+ transport.start();
+ }
+ mTransports.put(transport.getAssociationId(), transport);
+ }
+
public Future<?> requestPermissionRestore(int associationId, byte[] data) {
synchronized (mTransports) {
final Transport transport = mTransports.get(associationId);
if (transport == null) {
return CompletableFuture.failedFuture(new IOException("Missing transport"));
}
-
return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data);
}
}
@@ -146,10 +222,9 @@
this.mSecureTransportEnabled = enabled;
}
- private boolean isSecureTransportEnabled(int associationId) {
+ private boolean isSecureTransportEnabled() {
boolean enabled = !Build.IS_DEBUGGABLE || mSecureTransportEnabled;
- // TODO: version comparison logic
return enabled;
}
}
diff --git a/services/companion/java/com/android/server/companion/transport/CryptoManager.java b/services/companion/java/com/android/server/companion/transport/CryptoManager.java
index b08354a..a15939e 100644
--- a/services/companion/java/com/android/server/companion/transport/CryptoManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CryptoManager.java
@@ -16,51 +16,51 @@
package com.android.server.companion.transport;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
import android.util.Slog;
-import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableEntryException;
-import java.security.cert.CertificateException;
+import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
/**
- * This class can be used to encrypt and decrypt bytes using Android Cryptography
+ * This class uses Java Cryptography to encrypt and decrypt messages
*/
public class CryptoManager {
private static final String TAG = "CDM_CryptoManager";
+ private static final int SECRET_KEY_LENGTH = 32;
+ private static final String ALGORITHM = "AES";
+ private static final String TRANSFORMATION = "AES/CBC/PKCS7Padding";
- private static final String KEY_STORE_ALIAS = "cdm_secret";
- private static final String ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
- private static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC;
- private static final String PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7;
- private static final String TRANSFORMATION = ALGORITHM + "/" + BLOCK_MODE + "/" + PADDING;
+ private final byte[] mPreSharedKey;
+ private Cipher mEncryptCipher;
+ private Cipher mDecryptCipher;
- private final KeyStore mKeyStore;
+ private SecretKey mSecretKey;
- public CryptoManager() {
- // Initialize KeyStore
+ public CryptoManager(byte[] preSharedKey) {
+ if (preSharedKey == null) {
+ mPreSharedKey = Arrays.copyOf(new byte[0], SECRET_KEY_LENGTH);
+ } else {
+ mPreSharedKey = Arrays.copyOf(preSharedKey, SECRET_KEY_LENGTH);
+ }
+ mSecretKey = new SecretKeySpec(mPreSharedKey, ALGORITHM);
try {
- mKeyStore = KeyStore.getInstance("AndroidKeyStore");
- mKeyStore.load(null);
- } catch (KeyStoreException | IOException | NoSuchAlgorithmException
- | CertificateException e) {
- throw new RuntimeException(e);
+ mEncryptCipher = Cipher.getInstance(TRANSFORMATION);
+ mEncryptCipher.init(Cipher.ENCRYPT_MODE, mSecretKey);
+ mDecryptCipher = Cipher.getInstance(TRANSFORMATION);
+ } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
+ Slog.e(TAG, e.getMessage());
}
}
@@ -69,21 +69,19 @@
*/
public byte[] encrypt(byte[] input) {
try {
- // Encrypt using Cipher
- Cipher encryptCipher = Cipher.getInstance(TRANSFORMATION);
- encryptCipher.init(Cipher.ENCRYPT_MODE, getKey());
- byte[] encryptedBytes = encryptCipher.doFinal(input);
+ if (mEncryptCipher == null) {
+ return null;
+ }
- // Write to bytes
+ byte[] encryptedBytes = mEncryptCipher.doFinal(input);
ByteBuffer buffer = ByteBuffer.allocate(
- 4 + encryptCipher.getIV().length + 4 + encryptedBytes.length)
- .putInt(encryptCipher.getIV().length)
- .put(encryptCipher.getIV())
+ 4 + mEncryptCipher.getIV().length + 4 + encryptedBytes.length)
+ .putInt(mEncryptCipher.getIV().length)
+ .put(mEncryptCipher.getIV())
.putInt(encryptedBytes.length)
.put(encryptedBytes);
return buffer.array();
- } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
- | IllegalBlockSizeException | BadPaddingException e) {
+ } catch (IllegalBlockSizeException | BadPaddingException e) {
Slog.e(TAG, e.getMessage());
return null;
}
@@ -99,45 +97,20 @@
byte[] encryptedBytes = new byte[buffer.getInt()];
buffer.get(encryptedBytes);
try {
- Cipher decryptCipher = Cipher.getInstance(TRANSFORMATION);
- decryptCipher.init(Cipher.DECRYPT_MODE, getKey(), new IvParameterSpec(iv));
- return decryptCipher.doFinal(encryptedBytes);
- } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
- | InvalidAlgorithmParameterException | IllegalBlockSizeException
- | BadPaddingException e) {
+ mDecryptCipher.init(Cipher.DECRYPT_MODE, getKey(), new IvParameterSpec(iv));
+ return mDecryptCipher.doFinal(encryptedBytes);
+ } catch (InvalidKeyException | InvalidAlgorithmParameterException
+ | IllegalBlockSizeException | BadPaddingException e) {
Slog.e(TAG, e.getMessage());
return null;
}
}
private SecretKey getKey() {
- try {
- KeyStore.Entry keyEntry = mKeyStore.getEntry(KEY_STORE_ALIAS, null);
- if (keyEntry instanceof KeyStore.SecretKeyEntry
- && ((KeyStore.SecretKeyEntry) keyEntry).getSecretKey() != null) {
- return ((KeyStore.SecretKeyEntry) keyEntry).getSecretKey();
- } else {
- return createKey();
- }
- } catch (NoSuchAlgorithmException | UnrecoverableEntryException | KeyStoreException e) {
- throw new RuntimeException(e);
+ if (mSecretKey != null) {
+ return mSecretKey;
}
- }
-
- private SecretKey createKey() {
- try {
- KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
- keyGenerator.init(
- new KeyGenParameterSpec.Builder(KEY_STORE_ALIAS,
- KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
- .setBlockModes(BLOCK_MODE)
- .setEncryptionPaddings(PADDING)
- .setUserAuthenticationRequired(false)
- .setRandomizedEncryptionRequired(true)
- .build());
- return keyGenerator.generateKey();
- } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
- throw new RuntimeException(e);
- }
+ mSecretKey = new SecretKeySpec(mPreSharedKey, ALGORITHM);
+ return mSecretKey;
}
}
diff --git a/services/companion/java/com/android/server/companion/transport/RawTransport.java b/services/companion/java/com/android/server/companion/transport/RawTransport.java
index 7c0c7cf..4060f6e 100644
--- a/services/companion/java/com/android/server/companion/transport/RawTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/RawTransport.java
@@ -21,8 +21,6 @@
import android.os.ParcelFileDescriptor;
import android.util.Slog;
-import com.android.server.companion.transport.CompanionTransportManager.Listener;
-
import libcore.io.IoUtils;
import libcore.io.Streams;
@@ -32,8 +30,8 @@
class RawTransport extends Transport {
private volatile boolean mStopped;
- RawTransport(int associationId, ParcelFileDescriptor fd, Context context, Listener listener) {
- super(associationId, fd, context, listener);
+ RawTransport(int associationId, ParcelFileDescriptor fd, Context context) {
+ super(associationId, fd, context);
}
@Override
@@ -64,7 +62,7 @@
protected void sendMessage(int message, int sequence, @NonNull byte[] data)
throws IOException {
if (DEBUG) {
- Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message)
+ Slog.e(TAG, "Sending message 0x" + Integer.toHexString(message)
+ " sequence " + sequence + " length " + data.length
+ " to association " + mAssociationId);
}
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
index 4194130..cca0843 100644
--- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -21,8 +21,8 @@
import android.os.ParcelFileDescriptor;
import android.util.Slog;
+import com.android.server.companion.securechannel.AttestationVerifier;
import com.android.server.companion.securechannel.SecureChannel;
-import com.android.server.companion.transport.CompanionTransportManager.Listener;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -37,14 +37,17 @@
private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(100);
- SecureTransport(int associationId,
- ParcelFileDescriptor fd,
- Context context,
- Listener listener) {
- super(associationId, fd, context, listener);
+ SecureTransport(int associationId, ParcelFileDescriptor fd, Context context) {
+ super(associationId, fd, context);
mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, context);
}
+ SecureTransport(int associationId, ParcelFileDescriptor fd, Context context,
+ byte[] preSharedKey, AttestationVerifier verifier) {
+ super(associationId, fd, context);
+ mSecureChannel = new SecureChannel(mRemoteIn, mRemoteOut, this, preSharedKey, verifier);
+ }
+
@Override
public void start() {
mSecureChannel.start();
diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java
index 923d424..e984c63 100644
--- a/services/companion/java/com/android/server/companion/transport/Transport.java
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -25,23 +25,28 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.companion.transport.CompanionTransportManager.Listener;
import libcore.util.EmptyArray;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
-abstract class Transport {
+/**
+ * This class represents the channel established between two devices.
+ */
+public abstract class Transport {
protected static final String TAG = "CDM_CompanionTransport";
protected static final boolean DEBUG = Build.IS_DEBUGGABLE;
static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
- static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
+ public static final int MESSAGE_REQUEST_PLATFORM_INFO = 0x63807086; // ?PFV
+ public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
@@ -49,11 +54,24 @@
protected static final int HEADER_LENGTH = 12;
protected final int mAssociationId;
+ protected final ParcelFileDescriptor mFd;
protected final InputStream mRemoteIn;
protected final OutputStream mRemoteOut;
protected final Context mContext;
- private final Listener mListener;
+ /** Message type -> Listener */
+ private final Map<Integer, Listener> mListeners;
+
+ /**
+ * Message listener
+ */
+ public interface Listener {
+ /**
+ * Called when a message is received
+ * @param data data content in the message
+ */
+ void onDataReceived(byte[] data);
+ }
private static boolean isRequest(int message) {
return (message & 0xFF000000) == 0x63000000;
@@ -68,16 +86,36 @@
new SparseArray<>();
protected final AtomicInteger mNextSequence = new AtomicInteger();
- Transport(int associationId, ParcelFileDescriptor fd, Context context, Listener listener) {
- this.mAssociationId = associationId;
- this.mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
- this.mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
- this.mContext = context;
- this.mListener = listener;
+ Transport(int associationId, ParcelFileDescriptor fd, Context context) {
+ mAssociationId = associationId;
+ mFd = fd;
+ mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+ mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ mContext = context;
+ mListeners = new HashMap<>();
+ }
+
+ /**
+ * Add a listener when a message is received for the message type
+ * @param message Message type
+ * @param listener Execute when a message with the type is received
+ */
+ public void addListener(int message, Listener listener) {
+ mListeners.put(message, listener);
+ }
+
+ public int getAssociationId() {
+ return mAssociationId;
+ }
+
+ protected ParcelFileDescriptor getFd() {
+ return mFd;
}
public abstract void start();
public abstract void stop();
+ protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
+ throws IOException;
public Future<byte[]> requestForResponse(int message, byte[] data) {
if (DEBUG) Slog.d(TAG, "Requesting for response");
@@ -99,9 +137,6 @@
return pending;
}
- protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
- throws IOException;
-
protected final void handleMessage(int message, int sequence, @NonNull byte[] data)
throws IOException {
if (DEBUG) {
@@ -130,6 +165,11 @@
sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data);
break;
}
+ case MESSAGE_REQUEST_PLATFORM_INFO: {
+ callback(message, data);
+ sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
+ break;
+ }
case MESSAGE_REQUEST_PERMISSION_RESTORE: {
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
&& !Build.isDebuggable()) {
@@ -138,7 +178,7 @@
break;
}
try {
- mListener.onRequestPermissionRestore(data);
+ callback(message, data);
sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
} catch (Exception e) {
Slog.w(TAG, "Failed to restore permissions");
@@ -154,6 +194,12 @@
}
}
+ private void callback(int message, byte[] data) {
+ if (mListeners.containsKey(message)) {
+ mListeners.get(message).onDataReceived(data);
+ }
+ }
+
private void processResponse(int message, int sequence, byte[] data) {
final CompletableFuture<byte[]> future;
synchronized (mPendingRequests) {
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 3f1ad3a..2a46d86 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -16,18 +16,12 @@
package com.android.server;
-import static android.content.IntentFilter.BLOCK_NULL_ACTION_INTENTS;
-
-import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
-import android.os.Binder;
-import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.FastImmutableArraySet;
@@ -40,7 +34,6 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.FastPrintWriter;
-import com.android.server.am.ActivityManagerUtils;
import com.android.server.pm.Computer;
import com.android.server.pm.snapshot.PackageDataSnapshot;
@@ -88,7 +81,7 @@
* Returns whether an intent matches the IntentFilter with a pre-resolved type.
*/
public static boolean intentMatchesFilter(
- IntentFilter filter, Intent intent, String resolvedType, boolean blockNullAction) {
+ IntentFilter filter, Intent intent, String resolvedType) {
final boolean debug = localLOGV
|| ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
@@ -102,8 +95,7 @@
}
final int match = filter.match(intent.getAction(), resolvedType, intent.getScheme(),
- intent.getData(), intent.getCategories(), TAG, /* supportWildcards */ false,
- blockNullAction, null, null);
+ intent.getData(), intent.getCategories(), TAG);
if (match >= 0) {
if (debug) {
@@ -358,32 +350,14 @@
return Collections.unmodifiableSet(mFilters);
}
- private boolean blockNullAction(Computer computer, Intent intent,
- String resolvedType, int callingUid, boolean debug) {
- if (intent.getAction() == null) {
- final boolean blockNullAction = UserHandle.isCore(callingUid)
- || computer.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS, callingUid);
- ActivityManagerUtils.logUnsafeIntentEvent(
- UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH,
- callingUid, intent, resolvedType, blockNullAction);
- if (blockNullAction) {
- if (debug) Slog.v(TAG, "Skip matching filters: action is null");
- return true;
- }
- }
- return false;
- }
-
public List<R> queryIntentFromList(@NonNull Computer computer, Intent intent,
- String resolvedType, boolean defaultOnly, ArrayList<F[]> listCut,
- int callingUid, @UserIdInt int userId, long customFlags) {
+ String resolvedType, boolean defaultOnly, ArrayList<F[]> listCut, int userId,
+ long customFlags) {
ArrayList<R> resultList = new ArrayList<R>();
final boolean debug = localLOGV ||
((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
- if (blockNullAction(computer, intent, resolvedType, callingUid, debug)) return resultList;
-
FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
final String scheme = intent.getScheme();
int N = listCut.size();
@@ -391,26 +365,18 @@
buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType, scheme,
listCut.get(i), resultList, userId, customFlags);
}
- filterResults(computer, intent, resultList);
+ filterResults(resultList);
sortResults(resultList);
return resultList;
}
- public final List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
- String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
- return queryIntent(snapshot, intent, resolvedType, defaultOnly,
- Binder.getCallingUid(), userId, 0);
- }
-
public List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
- String resolvedType, boolean defaultOnly, int callingUid, @UserIdInt int userId) {
- return queryIntent(snapshot, intent, resolvedType, defaultOnly, callingUid, userId, 0);
+ String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
+ return queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, 0);
}
protected final List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
- String resolvedType, boolean defaultOnly, int callingUid, @UserIdInt int userId,
- long customFlags) {
- final Computer computer = (Computer) snapshot;
+ String resolvedType, boolean defaultOnly, @UserIdInt int userId, long customFlags) {
String scheme = intent.getScheme();
ArrayList<R> finalList = new ArrayList<R>();
@@ -422,8 +388,6 @@
TAG, "Resolving type=" + resolvedType + " scheme=" + scheme
+ " defaultOnly=" + defaultOnly + " userId=" + userId + " of " + intent);
- if (blockNullAction(computer, intent, resolvedType, callingUid, debug)) return finalList;
-
F[] firstTypeCut = null;
F[] secondTypeCut = null;
F[] thirdTypeCut = null;
@@ -484,6 +448,7 @@
}
FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
+ Computer computer = (Computer) snapshot;
if (firstTypeCut != null) {
buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType,
scheme, firstTypeCut, finalList, userId, customFlags);
@@ -500,7 +465,7 @@
buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType,
scheme, schemeCut, finalList, userId, customFlags);
}
- filterResults(computer, intent, finalList);
+ filterResults(finalList);
sortResults(finalList);
if (debug) {
@@ -569,8 +534,7 @@
/**
* Apply filtering to the results. This happens before the results are sorted.
*/
- protected void filterResults(@NonNull Computer computer, @NonNull Intent intent,
- List<R> results) {
+ protected void filterResults(List<R> results) {
}
protected void dumpFilter(PrintWriter out, String prefix, F filter) {
@@ -802,11 +766,7 @@
continue;
}
- match = intentFilter.match(action, resolvedType, scheme, data, categories, TAG,
- false /*supportWildcards*/,
- false /*blockNullAction: already handled*/,
- null /*ignoreActions*/,
- null /*extras*/);
+ match = intentFilter.match(action, resolvedType, scheme, data, categories, TAG);
if (match >= 0) {
if (debug) Slog.v(TAG, " Filter matched! match=0x" +
Integer.toHexString(match) + " hasDefault="
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 93effd9..a8054de 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1128,19 +1128,6 @@
}
@Override
- protected void filterResults(@NonNull Computer computer,
- @NonNull Intent intent, List<BroadcastFilter> results) {
- if (intent.getAction() != null) return;
- // When the resolved component is targeting U+, block null action intents
- for (int i = results.size() - 1; i >= 0; --i) {
- if (computer.isChangeEnabled(
- IntentFilter.BLOCK_NULL_ACTION_INTENTS, results.get(i).owningUid)) {
- results.remove(i);
- }
- }
- }
-
- @Override
protected IntentFilter getIntentFilter(@NonNull BroadcastFilter input) {
return input;
}
@@ -13948,19 +13935,11 @@
(intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) {
continue;
}
-
- final boolean blockNullAction = mPlatformCompat.isChangeEnabledInternal(
- IntentFilter.BLOCK_NULL_ACTION_INTENTS, callerApp.info);
// If intent has scheme "content", it will need to access
// provider that needs to lock mProviderMap in ActivityThread
// and also it may need to wait application response, so we
// cannot lock ActivityManagerService here.
- if (filter.match(intent.getAction(), intent.resolveType(resolver),
- intent.getScheme(), intent.getData(), intent.getCategories(), TAG,
- false /* supportWildcards */,
- blockNullAction,
- null /* ignoreActions */,
- intent.getExtras()) >= 0) {
+ if (filter.match(resolver, intent, true, TAG) >= 0) {
if (allSticky == null) {
allSticky = new ArrayList<Intent>();
}
@@ -14979,7 +14958,7 @@
}
List<BroadcastFilter> registeredReceiversForUser =
mReceiverResolver.queryIntent(snapshot, intent,
- resolvedType, false /*defaultOnly*/, callingUid, users[i]);
+ resolvedType, false /*defaultOnly*/, users[i]);
if (registeredReceivers == null) {
registeredReceivers = registeredReceiversForUser;
} else if (registeredReceiversForUser != null) {
@@ -14988,7 +14967,7 @@
}
} else {
registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
- resolvedType, false /*defaultOnly*/, callingUid, userId);
+ resolvedType, false /*defaultOnly*/, userId);
}
}
BroadcastQueue.traceEnd(cookie);
diff --git a/services/core/java/com/android/server/am/ActivityManagerUtils.java b/services/core/java/com/android/server/am/ActivityManagerUtils.java
index 01466b8..9be553c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerUtils.java
+++ b/services/core/java/com/android/server/am/ActivityManagerUtils.java
@@ -17,13 +17,11 @@
import android.app.ActivityThread;
import android.content.ContentResolver;
-import android.content.Intent;
import android.provider.Settings;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -135,25 +133,4 @@
public static int hashComponentNameForAtom(String shortInstanceName) {
return getUnsignedHashUnCached(shortInstanceName) ^ getAndroidIdHash();
}
-
- /**
- * Helper method to log an unsafe intent event.
- */
- public static void logUnsafeIntentEvent(int event, int callingUid,
- Intent intent, String resolvedType, boolean blocked) {
- String[] categories = intent.getCategories() == null ? new String[0]
- : intent.getCategories().toArray(String[]::new);
- String component = intent.getComponent() == null ? null
- : intent.getComponent().flattenToString();
- FrameworkStatsLog.write(FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED,
- event,
- callingUid,
- component,
- intent.getPackage(),
- intent.getAction(),
- categories,
- resolvedType,
- intent.getScheme(),
- blocked);
- }
}
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index fac7748..c232b36 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -678,8 +678,4 @@
@NonNull
Collection<SharedUserSetting> getAllSharedUsers();
-
- boolean isChangeEnabled(long changeId, int uid);
-
- boolean isChangeEnabled(long changeId, ApplicationInfo info);
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 21f661b..5984360 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -125,7 +125,6 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.compat.PlatformCompat;
import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.PackageDexUsage;
@@ -575,7 +574,8 @@
list = new ArrayList<>(1);
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- this, list, false, intent, resolvedType, filterCallingUid);
+ mInjector.getCompatibility(), mComponentResolver,
+ list, false, intent, resolvedType, filterCallingUid);
}
}
} else {
@@ -591,7 +591,7 @@
String callingPkgName = getInstantAppPackageName(filterCallingUid);
boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId);
lockedResult.result = maybeAddInstantAppInstaller(
- lockedResult.result, intent, resolvedType, flags, filterCallingUid,
+ lockedResult.result, intent, resolvedType, flags,
userId, resolveForStart, isRequesterInstantApp);
}
if (lockedResult.sortResult) {
@@ -604,7 +604,8 @@
if (originalIntent != null) {
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- this, list, false, originalIntent, resolvedType, filterCallingUid);
+ mInjector.getCompatibility(), mComponentResolver,
+ list, false, originalIntent, resolvedType, filterCallingUid);
}
return skipPostResolution ? list : applyPostResolutionFilter(
@@ -686,7 +687,8 @@
list = new ArrayList<>(1);
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- this, list, false, intent, resolvedType, callingUid);
+ mInjector.getCompatibility(), mComponentResolver,
+ list, false, intent, resolvedType, callingUid);
}
}
} else {
@@ -697,7 +699,8 @@
if (originalIntent != null) {
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- this, list, false, originalIntent, resolvedType, callingUid);
+ mInjector.getCompatibility(), mComponentResolver,
+ list, false, originalIntent, resolvedType, callingUid);
}
return list;
@@ -710,7 +713,7 @@
String pkgName = intent.getPackage();
if (pkgName == null) {
final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(this, intent,
- resolvedType, flags, callingUid, userId);
+ resolvedType, flags, userId);
if (resolveInfos == null) {
return Collections.emptyList();
}
@@ -720,7 +723,7 @@
final AndroidPackage pkg = mPackages.get(pkgName);
if (pkg != null) {
final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(this, intent,
- resolvedType, flags, pkg.getServices(), callingUid,
+ resolvedType, flags, pkg.getServices(),
userId);
if (resolveInfos == null) {
return Collections.emptyList();
@@ -750,7 +753,7 @@
{@link PackageManager.SKIP_CURRENT_PROFILE} set.
*/
result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
- intent, resolvedType, flags, filterCallingUid, userId), userId));
+ intent, resolvedType, flags, userId), userId));
}
addInstant = isInstantAppResolutionAllowed(intent, result, userId,
false /*skipPackageCheck*/, flags);
@@ -774,7 +777,7 @@
|| !shouldFilterApplication(setting, filterCallingUid, userId))) {
result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
intent, resolvedType, flags, setting.getAndroidPackage().getActivities(),
- filterCallingUid, userId), userId));
+ userId), userId));
}
if (result == null || result.size() == 0) {
// the caller wants to resolve for a particular package; however, there
@@ -1105,7 +1108,7 @@
return null;
}
List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(this, intent,
- resolvedType, flags, Binder.getCallingUid(), parentUserId);
+ resolvedType, flags, parentUserId);
if (resultTargetUser == null || resultTargetUser.isEmpty()) {
return null;
@@ -1343,7 +1346,7 @@
private List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result,
Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
- int callingUid, int userId, boolean resolveForStart, boolean isRequesterInstantApp) {
+ int userId, boolean resolveForStart, boolean isRequesterInstantApp) {
// first, check to see if we've got an instant app already installed
final boolean alreadyResolvedLocally = (flags & PackageManager.MATCH_INSTANT) != 0;
ResolveInfo localInstantApp = null;
@@ -1356,7 +1359,6 @@
| PackageManager.GET_RESOLVED_FILTER
| PackageManager.MATCH_INSTANT
| PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY,
- callingUid,
userId);
for (int i = instantApps.size() - 1; i >= 0; --i) {
final ResolveInfo info = instantApps.get(i);
@@ -3691,13 +3693,10 @@
ps, callingUid, component, TYPE_ACTIVITY, userId, true /* filterUninstall */)) {
return false;
}
- final boolean callerBlocksNullAction = isChangeEnabled(
- IntentFilter.BLOCK_NULL_ACTION_INTENTS, callingUid);
for (int i=0; i< a.getIntents().size(); i++) {
if (a.getIntents().get(i).getIntentFilter()
.match(intent.getAction(), resolvedType, intent.getScheme(),
- intent.getData(), intent.getCategories(), TAG,
- /*supportWildcards*/ false, callerBlocksNullAction, null, null) >= 0) {
+ intent.getData(), intent.getCategories(), TAG) >= 0) {
return true;
}
}
@@ -5795,37 +5794,4 @@
public UserInfo[] getUserInfos() {
return mInjector.getUserManagerInternal().getUserInfos();
}
-
- @Override
- public boolean isChangeEnabled(long changeId, int uid) {
- final PlatformCompat compat = mInjector.getCompatibility();
- int appId = UserHandle.getAppId(uid);
- SettingBase s = mSettings.getSettingBase(appId);
- if (s instanceof PackageSetting) {
- var ps = (PackageSetting) s;
- if (ps.getAndroidPackage() == null) return false;
- var info = new ApplicationInfo();
- info.packageName = ps.getPackageName();
- info.targetSdkVersion = ps.getAndroidPackage().getTargetSdkVersion();
- return compat.isChangeEnabledInternal(changeId, info);
- } else if (s instanceof SharedUserSetting) {
- var ss = (SharedUserSetting) s;
- List<AndroidPackage> packages = ss.getPackages();
- for (int i = 0; i < packages.size(); ++i) {
- var pkg = packages.get(i);
- var info = new ApplicationInfo();
- info.packageName = pkg.getPackageName();
- info.targetSdkVersion = pkg.getTargetSdkVersion();
- if (compat.isChangeEnabledInternal(changeId, info)) {
- return true;
- }
- }
- }
- return false;
- }
-
- @Override
- public boolean isChangeEnabled(long changeId, ApplicationInfo info) {
- return mInjector.getCompatibility().isChangeEnabledInternal(changeId, info);
- }
}
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java b/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java
index ee8f560..90d89c6 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java
@@ -23,7 +23,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
-import android.os.Binder;
import android.util.SparseBooleanArray;
import com.android.internal.app.IntentForwarderActivity;
@@ -279,7 +278,7 @@
}
List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(computer, intent,
- resolvedType, flags, Binder.getCallingUid(), targetUserId);
+ resolvedType, flags, targetUserId);
if (CollectionUtils.isEmpty(resultTargetUser)) {
return null;
}
diff --git a/services/core/java/com/android/server/pm/NoFilteringResolver.java b/services/core/java/com/android/server/pm/NoFilteringResolver.java
index e53e756..3923890 100644
--- a/services/core/java/com/android/server/pm/NoFilteringResolver.java
+++ b/services/core/java/com/android/server/pm/NoFilteringResolver.java
@@ -102,7 +102,7 @@
boolean hasNonNegativePriorityResult,
Function<String, PackageStateInternal> pkgSettingFunction) {
List<ResolveInfo> resolveInfos = mComponentResolver.queryActivities(computer,
- intent, resolvedType, flags, Binder.getCallingUid(), targetUserId);
+ intent, resolvedType, flags, targetUserId);
List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
if (resolveInfos != null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 9036d4c..928ffa7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -16,7 +16,6 @@
package com.android.server.pm;
-import static android.content.IntentFilter.BLOCK_NULL_ACTION_INTENTS;
import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
@@ -95,6 +94,7 @@
import com.android.server.IntentResolver;
import com.android.server.LocalManagerRegistry;
import com.android.server.Watchdog;
+import com.android.server.compat.PlatformCompat;
import com.android.server.pm.dex.PackageDexUsage;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
@@ -1166,8 +1166,10 @@
return (ps.getFlags() & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
}
+ // Static to give access to ComputeEngine
public static void applyEnforceIntentFilterMatching(
- Computer computer, List<ResolveInfo> resolveInfos, boolean isReceiver,
+ PlatformCompat compat, ComponentResolverApi resolver,
+ List<ResolveInfo> resolveInfos, boolean isReceiver,
Intent intent, String resolvedType, int filterCallingUid) {
if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
@@ -1175,11 +1177,6 @@
? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
: null;
- final boolean callerBlocksNullAction = computer.isChangeEnabled(
- BLOCK_NULL_ACTION_INTENTS, filterCallingUid);
-
- final ComponentResolverApi resolver = computer.getComponentResolver();
-
for (int i = resolveInfos.size() - 1; i >= 0; --i) {
final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
@@ -1190,15 +1187,11 @@
}
// Only enforce filter matching if target app's target SDK >= T
- if (!computer.isChangeEnabled(
+ if (!compat.isChangeEnabledInternal(
ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, info.applicationInfo)) {
continue;
}
- // Block null action intent if either source or target app's target SDK >= U
- final boolean blockNullAction = callerBlocksNullAction
- || computer.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS, info.applicationInfo);
-
final ParsedMainComponent comp;
if (info instanceof ActivityInfo) {
if (isReceiver) {
@@ -1220,8 +1213,7 @@
boolean match = false;
for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
- if (IntentResolver.intentMatchesFilter(
- intentFilter, intent, resolvedType, blockNullAction)) {
+ if (IntentResolver.intentMatchesFilter(intentFilter, intent, resolvedType)) {
match = true;
break;
}
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index 8bca4a9..a13c568 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -466,14 +466,15 @@
list = new ArrayList<>(1);
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- computer, list, true, intent, resolvedType, filterCallingUid);
+ mPlatformCompat, componentResolver, list, true, intent,
+ resolvedType, filterCallingUid);
}
}
} else {
String pkgName = intent.getPackage();
if (pkgName == null) {
- final List<ResolveInfo> result = componentResolver.queryReceivers(
- computer, intent, resolvedType, flags, filterCallingUid, userId);
+ final List<ResolveInfo> result = componentResolver
+ .queryReceivers(computer, intent, resolvedType, flags, userId);
if (result != null) {
list = result;
}
@@ -481,7 +482,7 @@
final AndroidPackage pkg = computer.getPackage(pkgName);
if (pkg != null) {
final List<ResolveInfo> result = componentResolver.queryReceivers(computer,
- intent, resolvedType, flags, pkg.getReceivers(), filterCallingUid, userId);
+ intent, resolvedType, flags, pkg.getReceivers(), userId);
if (result != null) {
list = result;
}
@@ -491,7 +492,8 @@
if (originalIntent != null) {
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- computer, list, true, originalIntent, resolvedType, filterCallingUid);
+ mPlatformCompat, componentResolver,
+ list, true, originalIntent, resolvedType, filterCallingUid);
}
return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid,
@@ -575,7 +577,7 @@
String pkgName = intent.getPackage();
if (pkgName == null) {
final List<ResolveInfo> resolveInfos = componentResolver.queryProviders(computer,
- intent, resolvedType, flags, callingUid, userId);
+ intent, resolvedType, flags, userId);
if (resolveInfos == null) {
return Collections.emptyList();
}
@@ -585,7 +587,7 @@
final AndroidPackage pkg = computer.getPackage(pkgName);
if (pkg != null) {
final List<ResolveInfo> resolveInfos = componentResolver.queryProviders(computer,
- intent, resolvedType, flags, pkg.getProviders(), callingUid, userId);
+ intent, resolvedType, flags, pkg.getProviders(), userId);
if (resolveInfos == null) {
return Collections.emptyList();
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index b7c3b97..d04ce5f 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -4350,14 +4350,8 @@
@NonNull ComponentName activity, @UserIdInt int userId) {
final long start = getStatStartTime();
try {
- final ActivityInfo ai;
- try {
- ai = mContext.getPackageManager().getActivityInfoAsUser(activity,
- PackageManager.ComponentInfoFlags.of(PACKAGE_MATCH_FLAGS), userId);
- } catch (NameNotFoundException e) {
- return false;
- }
- return ai.enabled && ai.exported;
+ return queryActivities(new Intent(), activity.getPackageName(), activity, userId)
+ .size() > 0;
} finally {
logDurationStat(Stats.IS_ACTIVITY_ENABLED, start);
}
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
index 977fab1..fac681a 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
@@ -807,10 +807,10 @@
}
}
- private abstract static class MimeGroupsAwareIntentResolver<F extends ParsedComponent>
- extends IntentResolver<Pair<F, ParsedIntentInfo>, ResolveInfo> {
- private final ArrayMap<String, Pair<F, ParsedIntentInfo>[]> mMimeGroupToFilter =
- new ArrayMap<>();
+ private abstract static class MimeGroupsAwareIntentResolver<F extends Pair<?
+ extends ParsedComponent, ParsedIntentInfo>, R>
+ extends IntentResolver<F, R> {
+ private final ArrayMap<String, F[]> mMimeGroupToFilter = new ArrayMap<>();
private boolean mIsUpdatingMimeGroup = false;
@NonNull
@@ -822,7 +822,7 @@
}
// Copy constructor used in creating snapshots
- MimeGroupsAwareIntentResolver(MimeGroupsAwareIntentResolver<F> orig,
+ MimeGroupsAwareIntentResolver(MimeGroupsAwareIntentResolver<F, R> orig,
@NonNull UserManagerService userManager) {
mUserManager = userManager;
copyFrom(orig);
@@ -831,7 +831,7 @@
}
@Override
- public void addFilter(@Nullable PackageDataSnapshot snapshot, Pair<F, ParsedIntentInfo> f) {
+ public void addFilter(@Nullable PackageDataSnapshot snapshot, F f) {
IntentFilter intentFilter = getIntentFilter(f);
// We assume Computer is available for this class and all subclasses. Because this class
// uses subclass method override to handle logic, the Computer parameter must be in the
@@ -846,7 +846,7 @@
}
@Override
- protected void removeFilterInternal(Pair<F, ParsedIntentInfo> f) {
+ protected void removeFilterInternal(F f) {
IntentFilter intentFilter = getIntentFilter(f);
if (!mIsUpdatingMimeGroup) {
unregister_intent_filter(f, intentFilter.mimeGroupsIterator(), mMimeGroupToFilter,
@@ -857,86 +857,6 @@
intentFilter.clearDynamicDataTypes();
}
- @Override
- public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
- String resolvedType, boolean defaultOnly, int callingUid, @UserIdInt int userId) {
- if (!mUserManager.exists(userId)) return null;
- long flags = (defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0);
- return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, callingUid,
- userId, flags);
- }
-
- List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
- String resolvedType, long flags, int callingUid, int userId) {
- if (!mUserManager.exists(userId)) return null;
- return super.queryIntent(computer, intent, resolvedType,
- (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, callingUid, userId, flags);
- }
-
- List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
- String resolvedType, long flags, List<F> packageComponents,
- int callingUid, int userId) {
- if (!mUserManager.exists(userId)) {
- return null;
- }
- if (packageComponents == null) {
- return Collections.emptyList();
- }
- final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
- final int componentsSize = packageComponents.size();
- ArrayList<Pair<F, ParsedIntentInfo>[]> listCut = new ArrayList<>(componentsSize);
-
- List<ParsedIntentInfo> intentFilters;
- for (int i = 0; i < componentsSize; ++i) {
- F component = packageComponents.get(i);
- intentFilters = component.getIntents();
- if (!intentFilters.isEmpty()) {
- Pair<F, ParsedIntentInfo>[] array = newArray(intentFilters.size());
- for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) {
- array[arrayIndex] = Pair.create(component, intentFilters.get(arrayIndex));
- }
- listCut.add(array);
- }
- }
- return super.queryIntentFromList(computer, intent, resolvedType,
- defaultOnly, listCut, callingUid, userId, flags);
- }
-
- @Override
- protected boolean isPackageForFilter(String packageName, Pair<F, ParsedIntentInfo> info) {
- return packageName.equals(info.first.getPackageName());
- }
-
- @Override
- protected void sortResults(List<ResolveInfo> results) {
- results.sort(RESOLVE_PRIORITY_SORTER);
- }
-
- @Override
- protected void filterResults(@NonNull Computer computer, @NonNull Intent intent,
- List<ResolveInfo> results) {
- if (intent.getAction() != null) return;
- // When the resolved component is targeting U+, block null action intents
- for (int i = results.size() - 1; i >= 0; --i) {
- if (computer.isChangeEnabled(IntentFilter.BLOCK_NULL_ACTION_INTENTS,
- results.get(i).getComponentInfo().applicationInfo)) {
- results.remove(i);
- }
- }
- }
-
- @Override
- protected Pair<F, ParsedIntentInfo>[] newArray(int size) {
- //noinspection unchecked
- return (Pair<F, ParsedIntentInfo>[]) new Pair<?, ?>[size];
- }
-
- @Override
- protected IntentFilter getIntentFilter(
- @NonNull Pair<F, ParsedIntentInfo> input) {
- return input.second.getIntentFilter();
- }
-
/**
* Updates MIME group by applying changes to all IntentFilters
* that contain the group and repopulating m*ToFilter maps accordingly
@@ -947,12 +867,12 @@
*/
public boolean updateMimeGroup(@NonNull Computer computer, String packageName,
String mimeGroup) {
- Pair<F, ParsedIntentInfo>[] filters = mMimeGroupToFilter.get(mimeGroup);
+ F[] filters = mMimeGroupToFilter.get(mimeGroup);
int n = filters != null ? filters.length : 0;
mIsUpdatingMimeGroup = true;
boolean hasChanges = false;
- Pair<F, ParsedIntentInfo> filter;
+ F filter;
for (int i = 0; i < n && (filter = filters[i]) != null; i++) {
if (isPackageForFilter(packageName, filter)) {
hasChanges |= updateFilter(computer, filter);
@@ -962,7 +882,7 @@
return hasChanges;
}
- private boolean updateFilter(@NonNull Computer computer, Pair<F, ParsedIntentInfo> f) {
+ private boolean updateFilter(@NonNull Computer computer, F f) {
IntentFilter filter = getIntentFilter(f);
List<String> oldTypes = filter.dataTypes();
removeFilter(f);
@@ -987,7 +907,7 @@
return first.equals(second);
}
- private void applyMimeGroups(@NonNull Computer computer, Pair<F, ParsedIntentInfo> f) {
+ private void applyMimeGroups(@NonNull Computer computer, F f) {
IntentFilter filter = getIntentFilter(f);
for (int i = filter.countMimeGroups() - 1; i >= 0; i--) {
@@ -1011,8 +931,8 @@
}
@Override
- protected boolean isFilterStopped(@NonNull Computer computer,
- Pair<F, ParsedIntentInfo> filter, @UserIdInt int userId) {
+ protected boolean isFilterStopped(@NonNull Computer computer, F filter,
+ @UserIdInt int userId) {
if (!mUserManager.exists(userId)) {
return true;
}
@@ -1028,7 +948,7 @@
}
public static class ActivityIntentResolver
- extends MimeGroupsAwareIntentResolver<ParsedActivity> {
+ extends MimeGroupsAwareIntentResolver<Pair<ParsedActivity, ParsedIntentInfo>, ResolveInfo> {
@NonNull
private UserNeedsBadgingCache mUserNeedsBadging;
@@ -1049,6 +969,53 @@
mUserNeedsBadging = userNeedsBadgingCache;
}
+ @Override
+ public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
+ String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
+ if (!mUserManager.exists(userId)) return null;
+ long flags = (defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0);
+ return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags);
+ }
+
+ List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
+ String resolvedType, long flags, int userId) {
+ if (!mUserManager.exists(userId)) {
+ return null;
+ }
+ return super.queryIntent(computer, intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags);
+ }
+
+ List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
+ String resolvedType, long flags, List<ParsedActivity> packageActivities,
+ int userId) {
+ if (!mUserManager.exists(userId)) {
+ return null;
+ }
+ if (packageActivities == null) {
+ return Collections.emptyList();
+ }
+ final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
+ final int activitiesSize = packageActivities.size();
+ ArrayList<Pair<ParsedActivity, ParsedIntentInfo>[]> listCut =
+ new ArrayList<>(activitiesSize);
+
+ List<ParsedIntentInfo> intentFilters;
+ for (int i = 0; i < activitiesSize; ++i) {
+ ParsedActivity activity = packageActivities.get(i);
+ intentFilters = activity.getIntents();
+ if (!intentFilters.isEmpty()) {
+ Pair<ParsedActivity, ParsedIntentInfo>[] array = newArray(intentFilters.size());
+ for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) {
+ array[arrayIndex] = Pair.create(activity, intentFilters.get(arrayIndex));
+ }
+ listCut.add(array);
+ }
+ }
+ return super.queryIntentFromList(computer, intent, resolvedType,
+ defaultOnly, listCut, userId, flags);
+ }
+
protected void addActivity(@NonNull Computer computer, ParsedActivity a, String type,
List<Pair<ParsedActivity, ParsedIntentInfo>> newIntents) {
mActivities.put(a.getComponentName(), a);
@@ -1105,6 +1072,18 @@
return true;
}
+ @Override
+ protected Pair<ParsedActivity, ParsedIntentInfo>[] newArray(int size) {
+ //noinspection unchecked
+ return (Pair<ParsedActivity, ParsedIntentInfo>[]) new Pair<?, ?>[size];
+ }
+
+ @Override
+ protected boolean isPackageForFilter(String packageName,
+ Pair<ParsedActivity, ParsedIntentInfo> info) {
+ return packageName.equals(info.first.getPackageName());
+ }
+
private void log(String reason, ParsedIntentInfo info, int match,
int userId) {
Slog.w(TAG, reason
@@ -1220,6 +1199,11 @@
}
@Override
+ protected void sortResults(List<ResolveInfo> results) {
+ results.sort(RESOLVE_PRIORITY_SORTER);
+ }
+
+ @Override
protected void dumpFilter(PrintWriter out, String prefix,
Pair<ParsedActivity, ParsedIntentInfo> pair) {
ParsedActivity activity = pair.first;
@@ -1253,6 +1237,12 @@
out.println();
}
+ @Override
+ protected IntentFilter getIntentFilter(
+ @NonNull Pair<ParsedActivity, ParsedIntentInfo> input) {
+ return input.second.getIntentFilter();
+ }
+
protected List<ParsedActivity> getResolveList(AndroidPackage pkg) {
return pkg.getActivities();
}
@@ -1288,7 +1278,7 @@
}
public static final class ProviderIntentResolver
- extends MimeGroupsAwareIntentResolver<ParsedProvider> {
+ extends MimeGroupsAwareIntentResolver<Pair<ParsedProvider, ParsedIntentInfo>, ResolveInfo> {
// Default constructor
ProviderIntentResolver(@NonNull UserManagerService userManager) {
super(userManager);
@@ -1301,6 +1291,57 @@
mProviders.putAll(orig.mProviders);
}
+ @Override
+ public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
+ String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
+ if (!mUserManager.exists(userId)) {
+ return null;
+ }
+ long flags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
+ return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags);
+ }
+
+ @Nullable
+ List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
+ String resolvedType, long flags, int userId) {
+ if (!mUserManager.exists(userId)) {
+ return null;
+ }
+ return super.queryIntent(computer, intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags);
+ }
+
+ @Nullable
+ List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
+ String resolvedType, long flags, List<ParsedProvider> packageProviders,
+ int userId) {
+ if (!mUserManager.exists(userId)) {
+ return null;
+ }
+ if (packageProviders == null) {
+ return Collections.emptyList();
+ }
+ final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
+ final int providersSize = packageProviders.size();
+ ArrayList<Pair<ParsedProvider, ParsedIntentInfo>[]> listCut =
+ new ArrayList<>(providersSize);
+
+ List<ParsedIntentInfo> intentFilters;
+ for (int i = 0; i < providersSize; ++i) {
+ ParsedProvider provider = packageProviders.get(i);
+ intentFilters = provider.getIntents();
+ if (!intentFilters.isEmpty()) {
+ Pair<ParsedProvider, ParsedIntentInfo>[] array = newArray(intentFilters.size());
+ for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) {
+ array[arrayIndex] = Pair.create(provider, intentFilters.get(arrayIndex));
+ }
+ listCut.add(array);
+ }
+ }
+ return super.queryIntentFromList(computer, intent, resolvedType,
+ defaultOnly, listCut, userId, flags);
+ }
+
void addProvider(@NonNull Computer computer, ParsedProvider p) {
if (mProviders.containsKey(p.getComponentName())) {
Slog.w(TAG, "Provider " + p.getComponentName() + " already defined; ignoring");
@@ -1361,6 +1402,18 @@
}
@Override
+ protected Pair<ParsedProvider, ParsedIntentInfo>[] newArray(int size) {
+ //noinspection unchecked
+ return (Pair<ParsedProvider, ParsedIntentInfo>[]) new Pair<?, ?>[size];
+ }
+
+ @Override
+ protected boolean isPackageForFilter(String packageName,
+ Pair<ParsedProvider, ParsedIntentInfo> info) {
+ return packageName.equals(info.first.getPackageName());
+ }
+
+ @Override
protected ResolveInfo newResult(@NonNull Computer computer,
Pair<ParsedProvider, ParsedIntentInfo> pair, int match, int userId,
long customFlags) {
@@ -1426,6 +1479,11 @@
}
@Override
+ protected void sortResults(List<ResolveInfo> results) {
+ results.sort(RESOLVE_PRIORITY_SORTER);
+ }
+
+ @Override
protected void dumpFilter(PrintWriter out, String prefix,
Pair<ParsedProvider, ParsedIntentInfo> pair) {
ParsedProvider provider = pair.first;
@@ -1460,11 +1518,17 @@
out.println();
}
+ @Override
+ protected IntentFilter getIntentFilter(
+ @NonNull Pair<ParsedProvider, ParsedIntentInfo> input) {
+ return input.second.getIntentFilter();
+ }
+
final ArrayMap<ComponentName, ParsedProvider> mProviders = new ArrayMap<>();
}
public static final class ServiceIntentResolver
- extends MimeGroupsAwareIntentResolver<ParsedService> {
+ extends MimeGroupsAwareIntentResolver<Pair<ParsedService, ParsedIntentInfo>, ResolveInfo> {
// Default constructor
ServiceIntentResolver(@NonNull UserManagerService userManager) {
super(userManager);
@@ -1477,6 +1541,50 @@
mServices.putAll(orig.mServices);
}
+ @Override
+ public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
+ String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
+ if (!mUserManager.exists(userId)) {
+ return null;
+ }
+ long flags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
+ return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags);
+ }
+
+ List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
+ String resolvedType, long flags, int userId) {
+ if (!mUserManager.exists(userId)) return null;
+ return super.queryIntent(computer, intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags);
+ }
+
+ List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
+ String resolvedType, long flags, List<ParsedService> packageServices, int userId) {
+ if (!mUserManager.exists(userId)) return null;
+ if (packageServices == null) {
+ return Collections.emptyList();
+ }
+ final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
+ final int servicesSize = packageServices.size();
+ ArrayList<Pair<ParsedService, ParsedIntentInfo>[]> listCut =
+ new ArrayList<>(servicesSize);
+
+ List<ParsedIntentInfo> intentFilters;
+ for (int i = 0; i < servicesSize; ++i) {
+ ParsedService service = packageServices.get(i);
+ intentFilters = service.getIntents();
+ if (intentFilters.size() > 0) {
+ Pair<ParsedService, ParsedIntentInfo>[] array = newArray(intentFilters.size());
+ for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) {
+ array[arrayIndex] = Pair.create(service, intentFilters.get(arrayIndex));
+ }
+ listCut.add(array);
+ }
+ }
+ return super.queryIntentFromList(computer, intent, resolvedType,
+ defaultOnly, listCut, userId, flags);
+ }
+
void addService(@NonNull Computer computer, ParsedService s) {
mServices.put(s.getComponentName(), s);
if (DEBUG_SHOW_INFO) {
@@ -1532,6 +1640,18 @@
}
@Override
+ protected Pair<ParsedService, ParsedIntentInfo>[] newArray(int size) {
+ //noinspection unchecked
+ return (Pair<ParsedService, ParsedIntentInfo>[]) new Pair<?, ?>[size];
+ }
+
+ @Override
+ protected boolean isPackageForFilter(String packageName,
+ Pair<ParsedService, ParsedIntentInfo> info) {
+ return packageName.equals(info.first.getPackageName());
+ }
+
+ @Override
protected ResolveInfo newResult(@NonNull Computer computer,
Pair<ParsedService, ParsedIntentInfo> pair, int match, int userId,
long customFlags) {
@@ -1590,6 +1710,11 @@
}
@Override
+ protected void sortResults(List<ResolveInfo> results) {
+ results.sort(RESOLVE_PRIORITY_SORTER);
+ }
+
+ @Override
protected void dumpFilter(PrintWriter out, String prefix,
Pair<ParsedService, ParsedIntentInfo> pair) {
ParsedService service = pair.first;
@@ -1627,6 +1752,12 @@
out.println();
}
+ @Override
+ protected IntentFilter getIntentFilter(
+ @NonNull Pair<ParsedService, ParsedIntentInfo> input) {
+ return input.second.getIntentFilter();
+ }
+
// Keys are String (activity class name), values are Activity.
final ArrayMap<ComponentName, ParsedService> mServices = new ArrayMap<>();
}
@@ -1690,8 +1821,7 @@
}
@Override
- protected void filterResults(@NonNull Computer computer,
- @NonNull Intent intent, List<AuxiliaryResolveInfo.AuxiliaryFilter> results) {
+ protected void filterResults(List<AuxiliaryResolveInfo.AuxiliaryFilter> results) {
// only do work if ordering is enabled [most of the time it won't be]
if (mOrderResult.size() == 0) {
return;
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java
index 7f88660..b8e4c8d 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java
@@ -55,12 +55,12 @@
@Nullable
List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId);
+ @Nullable String resolvedType, long flags, @UserIdInt int userId);
@Nullable
List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities,
- int callingUid, @UserIdInt int userId);
+ @UserIdInt int userId);
@Nullable
ProviderInfo queryProvider(@NonNull Computer computer, @NonNull String authority, long flags,
@@ -68,12 +68,12 @@
@Nullable
List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId);
+ @Nullable String resolvedType, long flags, @UserIdInt int userId);
@Nullable
List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers,
- int callingUid, @UserIdInt int userId);
+ @UserIdInt int userId);
@Nullable
List<ProviderInfo> queryProviders(@NonNull Computer computer, @Nullable String processName,
@@ -81,21 +81,21 @@
@Nullable
List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId);
+ @Nullable String resolvedType, long flags, @UserIdInt int userId);
@Nullable
List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers,
- int callingUid, @UserIdInt int userId);
+ @UserIdInt int userId);
@Nullable
List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId);
+ @Nullable String resolvedType, long flags, @UserIdInt int userId);
@Nullable
List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedService> services,
- int callingUid, @UserIdInt int userId);
+ @UserIdInt int userId);
void querySyncProviders(@NonNull Computer computer, @NonNull List<String> outNames,
@NonNull List<ProviderInfo> outInfo, boolean safeMode, @UserIdInt int userId);
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
index 6899924..9115775 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
@@ -126,17 +126,17 @@
@Nullable
@Override
public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int callingUid, int userId) {
- return mActivities.queryIntent(computer, intent, resolvedType, flags, callingUid, userId);
+ @Nullable String resolvedType, long flags, int userId) {
+ return mActivities.queryIntent(computer, intent, resolvedType, flags, userId);
}
@Nullable
@Override
public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities,
- int callingUid, int userId) {
+ int userId) {
return mActivities.queryIntentForPackage(computer, intent, resolvedType, flags, activities,
- callingUid, userId);
+ userId);
}
@Nullable
@@ -168,17 +168,17 @@
@Nullable
@Override
public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int callingUid, int userId) {
- return mProviders.queryIntent(computer, intent, resolvedType, flags, callingUid, userId);
+ @Nullable String resolvedType, long flags, int userId) {
+ return mProviders.queryIntent(computer, intent, resolvedType, flags, userId);
}
@Nullable
@Override
public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers,
- int callingUid, @UserIdInt int userId) {
+ @UserIdInt int userId) {
return mProviders.queryIntentForPackage(computer, intent, resolvedType, flags, providers,
- callingUid, userId);
+ userId);
}
@Nullable
@@ -241,33 +241,33 @@
@Nullable
@Override
public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int callingUid, int userId) {
- return mReceivers.queryIntent(computer, intent, resolvedType, flags, callingUid, userId);
+ @Nullable String resolvedType, long flags, int userId) {
+ return mReceivers.queryIntent(computer, intent, resolvedType, flags, userId);
}
@Nullable
@Override
public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers,
- int callingUid, @UserIdInt int userId) {
+ @UserIdInt int userId) {
return mReceivers.queryIntentForPackage(computer, intent, resolvedType, flags, receivers,
- callingUid, userId);
+ userId);
}
@Nullable
@Override
public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
- return mServices.queryIntent(computer, intent, resolvedType, flags, callingUid, userId);
+ @Nullable String resolvedType, long flags, @UserIdInt int userId) {
+ return mServices.queryIntent(computer, intent, resolvedType, flags, userId);
}
@Nullable
@Override
public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedService> services,
- int callingUid, @UserIdInt int userId) {
+ @UserIdInt int userId) {
return mServices.queryIntentForPackage(computer, intent, resolvedType, flags, services,
- callingUid, userId);
+ userId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java
index 5bfb135..0c84f4c 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java
@@ -92,9 +92,9 @@
@Nullable
@Override
public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
+ @Nullable String resolvedType, long flags, @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryActivities(computer, intent, resolvedType, flags, callingUid, userId);
+ return super.queryActivities(computer, intent, resolvedType, flags, userId);
}
}
@@ -102,10 +102,9 @@
@Override
public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities,
- int callingUid, @UserIdInt int userId) {
+ @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryActivities(computer, intent, resolvedType, flags, activities,
- callingUid, userId);
+ return super.queryActivities(computer, intent, resolvedType, flags, activities, userId);
}
}
@@ -121,9 +120,9 @@
@Nullable
@Override
public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
+ @Nullable String resolvedType, long flags, @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryProviders(computer, intent, resolvedType, flags, callingUid, userId);
+ return super.queryProviders(computer, intent, resolvedType, flags, userId);
}
}
@@ -131,10 +130,9 @@
@Override
public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers,
- int callingUid, @UserIdInt int userId) {
+ @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryProviders(computer, intent, resolvedType, flags, providers,
- callingUid, userId);
+ return super.queryProviders(computer, intent, resolvedType, flags, providers, userId);
}
}
@@ -151,9 +149,9 @@
@Nullable
@Override
public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
+ @Nullable String resolvedType, long flags, @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryReceivers(computer, intent, resolvedType, flags, callingUid, userId);
+ return super.queryReceivers(computer, intent, resolvedType, flags, userId);
}
}
@@ -161,19 +159,18 @@
@Override
public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers,
- int callingUid, @UserIdInt int userId) {
+ @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryReceivers(computer, intent, resolvedType, flags, receivers,
- callingUid, userId);
+ return super.queryReceivers(computer, intent, resolvedType, flags, receivers, userId);
}
}
@Nullable
@Override
public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
+ @Nullable String resolvedType, long flags, @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryServices(computer, intent, resolvedType, flags, callingUid, userId);
+ return super.queryServices(computer, intent, resolvedType, flags, userId);
}
}
@@ -181,10 +178,9 @@
@Override
public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedService> services,
- int callingUid, @UserIdInt int userId) {
+ @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryServices(computer, intent, resolvedType, flags, services, callingUid,
- userId);
+ return super.queryServices(computer, intent, resolvedType, flags, services, userId);
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index cc81d52..0a833f4 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3512,10 +3512,7 @@
info.isKeyguardOccluded =
mAtmService.mKeyguardController.isDisplayOccluded(DEFAULT_DISPLAY);
- info.startingWindowTypeParameter = activity.mStartingData != null
- ? activity.mStartingData.mTypeParams
- : (StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED
- | StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS);
+ info.startingWindowTypeParameter = activity.mStartingData.mTypeParams;
if ((info.startingWindowTypeParameter
& StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED) != 0) {
final WindowState topMainWin = getWindow(w -> w.mAttrs.type == TYPE_BASE_APPLICATION);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index b131365..3a30e4b 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.TaskInfo.cameraCompatControlStateToString;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
@@ -44,7 +43,6 @@
import android.view.SurfaceControl;
import android.window.ITaskOrganizer;
import android.window.ITaskOrganizerController;
-import android.window.IWindowlessStartingSurfaceCallback;
import android.window.SplashScreenView;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
@@ -658,10 +656,9 @@
info.splashScreenThemeResId = launchTheme;
}
info.taskSnapshot = taskSnapshot;
- info.appToken = activity.token;
// make this happen prior than prepare surface
try {
- lastOrganizer.addStartingWindow(info);
+ lastOrganizer.addStartingWindow(info, activity.token);
} catch (RemoteException e) {
Slog.e(TAG, "Exception sending onTaskStart callback", e);
return false;
@@ -707,55 +704,6 @@
}
}
- /**
- * Create a starting surface which attach on a given surface.
- * @param activity Target activity, this isn't necessary to be the top activity.
- * @param root The root surface which the created surface will attach on.
- * @param taskSnapshot Whether to draw snapshot.
- * @param callback Called when surface is drawn and attached to the root surface.
- * @return The taskId, this is a token and should be used to remove the surface, even if
- * the task was removed from hierarchy.
- */
- int addWindowlessStartingSurface(Task task, ActivityRecord activity, SurfaceControl root,
- TaskSnapshot taskSnapshot, IWindowlessStartingSurfaceCallback callback) {
- final Task rootTask = task.getRootTask();
- if (rootTask == null) {
- return INVALID_TASK_ID;
- }
- final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
- if (lastOrganizer == null) {
- return INVALID_TASK_ID;
- }
- final StartingWindowInfo info = task.getStartingWindowInfo(activity);
- info.taskInfo.taskDescription = activity.taskDescription;
- info.taskSnapshot = taskSnapshot;
- info.windowlessStartingSurfaceCallback = callback;
- info.rootSurface = root;
- try {
- lastOrganizer.addStartingWindow(info);
- } catch (RemoteException e) {
- Slog.e(TAG, "Exception sending addWindowlessStartingSurface ", e);
- return INVALID_TASK_ID;
- }
- return task.mTaskId;
- }
-
- void removeWindowlessStartingSurface(int taskId, boolean immediately) {
- final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
- if (lastOrganizer == null || taskId == 0) {
- return;
- }
- final StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
- removalInfo.taskId = taskId;
- removalInfo.windowlessSurface = true;
- removalInfo.removeImmediately = immediately;
- try {
- lastOrganizer.removeStartingWindow(removalInfo);
- } catch (RemoteException e) {
- Slog.e(TAG, "Exception sending removeWindowlessStartingSurface ", e);
- }
- }
-
boolean copySplashScreenView(Task task) {
final Task rootTask = task.getRootTask();
if (rootTask == null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 3f5d113..06ba5dd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -664,7 +664,7 @@
private fun mockQueryActivities(action: String, vararg activities: ActivityInfo) {
whenever(mocks.componentResolver.queryActivities(any(),
argThat { intent: Intent? -> intent != null && (action == intent.action) },
- nullable(), anyLong(), anyInt(), anyInt())) {
+ nullable(), anyLong(), anyInt())) {
ArrayList(activities.asList().map { info: ActivityInfo? ->
ResolveInfo().apply { activityInfo = info }
})
@@ -674,7 +674,7 @@
private fun mockQueryServices(action: String, vararg services: ServiceInfo) {
whenever(mocks.componentResolver.queryServices(any(),
argThat { intent: Intent? -> intent != null && (action == intent.action) },
- nullable(), anyLong(), anyInt(), anyInt())) {
+ nullable(), anyLong(), anyInt())) {
ArrayList(services.asList().map { info ->
ResolveInfo().apply { serviceInfo = info }
})
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index d7e4c55..169586e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1023,7 +1023,7 @@
RunningTaskInfo mInfo;
@Override
- public void addStartingWindow(StartingWindowInfo info) { }
+ public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { }
@Override
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { }
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index fafe90a..323894ca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1583,10 +1583,10 @@
}
@Override
- public void addStartingWindow(StartingWindowInfo info) {
+ public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
synchronized (mWMService.mGlobalLock) {
final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
- info.appToken);
+ appToken);
IWindow iWindow = mock(IWindow.class);
doReturn(mock(IBinder.class)).when(iWindow).asBinder();
final WindowState window = WindowTestsBase.createWindow(null,
@@ -1596,8 +1596,8 @@
iWindow,
mPowerManagerWrapper);
activity.mStartingWindow = window;
- mAppWindowMap.put(info.appToken, window);
- mTaskAppMap.put(info.taskInfo.taskId, info.appToken);
+ mAppWindowMap.put(appToken, window);
+ mTaskAppMap.put(info.taskInfo.taskId, appToken);
}
if (mRunnableWhenAddingSplashScreen != null) {
mRunnableWhenAddingSplashScreen.run();
diff --git a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
index 0dcf8ce..b1cd994 100644
--- a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.usb.UsbConfiguration;
-import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
@@ -35,9 +34,12 @@
import android.service.usb.UsbDirectMidiDeviceProto;
import android.util.Log;
+import com.android.internal.midi.MidiEventMultiScheduler;
import com.android.internal.midi.MidiEventScheduler;
import com.android.internal.midi.MidiEventScheduler.MidiEvent;
import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.server.usb.descriptors.UsbACMidi10Endpoint;
+import com.android.server.usb.descriptors.UsbDescriptor;
import com.android.server.usb.descriptors.UsbDescriptorParser;
import com.android.server.usb.descriptors.UsbEndpointDescriptor;
import com.android.server.usb.descriptors.UsbInterfaceDescriptor;
@@ -45,6 +47,7 @@
import libcore.io.IoUtils;
+import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -56,7 +59,7 @@
*/
public final class UsbDirectMidiDevice implements Closeable {
private static final String TAG = "UsbDirectMidiDevice";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private Context mContext;
private String mName;
@@ -74,9 +77,6 @@
private MidiDeviceServer mServer;
- // event schedulers for each input port of the physical device
- private MidiEventScheduler[] mEventSchedulers;
-
// Timeout for sending a packet to a device.
// If bulkTransfer times out, retry sending the packet up to 20 times.
private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 50;
@@ -86,8 +86,21 @@
private static final int THREAD_JOIN_TIMEOUT_MILLISECONDS = 200;
private ArrayList<UsbDeviceConnection> mUsbDeviceConnections;
+
+ // Array of endpoints by device connection.
private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints;
private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints;
+
+ // Array of cable counts by device connection.
+ // Each number here maps to an entry in mInputUsbEndpoints or mOutputUsbEndpoints.
+ // This is needed because this info is part of UsbEndpointDescriptor but not UsbEndpoint.
+ private ArrayList<ArrayList<Integer>> mInputUsbEndpointCableCounts;
+ private ArrayList<ArrayList<Integer>> mOutputUsbEndpointCableCounts;
+
+ // Array of event schedulers by device connection.
+ // Each number here maps to an entry in mOutputUsbEndpoints.
+ private ArrayList<ArrayList<MidiEventMultiScheduler>> mMidiEventMultiSchedulers;
+
private ArrayList<Thread> mThreads;
private UsbMidiBlockParser mMidiBlockParser = new UsbMidiBlockParser();
@@ -97,8 +110,6 @@
private boolean mIsOpen;
private boolean mServerAvailable;
- private UsbMidiPacketConverter mUsbMidiPacketConverter;
-
private PowerBoostSetter mPowerBoostSetter = null;
private static final byte MESSAGE_TYPE_MIDI_1_CHANNEL_VOICE = 0x02;
@@ -238,9 +249,9 @@
interfaceDescriptor.getEndpointDescriptor(endpointIndex);
// 0 is output, 1 << 7 is input.
if (endpoint.getDirection() == 0) {
- numOutputs++;
+ numOutputs += getNumJacks(endpoint);
} else {
- numInputs++;
+ numInputs += getNumJacks(endpoint);
}
}
}
@@ -307,19 +318,21 @@
Log.d(TAG, "openLocked()");
UsbManager manager = mContext.getSystemService(UsbManager.class);
- // Converting from raw MIDI to USB MIDI is not thread-safe.
- // UsbMidiPacketConverter creates a converter from raw MIDI
- // to USB MIDI for each USB output.
- mUsbMidiPacketConverter = new UsbMidiPacketConverter(mNumOutputs);
-
mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>();
mInputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>();
mOutputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>();
+ mInputUsbEndpointCableCounts = new ArrayList<ArrayList<Integer>>();
+ mOutputUsbEndpointCableCounts = new ArrayList<ArrayList<Integer>>();
+ mMidiEventMultiSchedulers = new ArrayList<ArrayList<MidiEventMultiScheduler>>();
mThreads = new ArrayList<Thread>();
for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) {
ArrayList<UsbEndpoint> inputEndpoints = new ArrayList<UsbEndpoint>();
ArrayList<UsbEndpoint> outputEndpoints = new ArrayList<UsbEndpoint>();
+ ArrayList<Integer> inputEndpointCableCounts = new ArrayList<Integer>();
+ ArrayList<Integer> outputEndpointCableCounts = new ArrayList<Integer>();
+ ArrayList<MidiEventMultiScheduler> midiEventMultiSchedulers =
+ new ArrayList<MidiEventMultiScheduler>();
UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex);
for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints();
endpointIndex++) {
@@ -328,8 +341,13 @@
// 0 is output, 1 << 7 is input.
if (endpoint.getDirection() == 0) {
outputEndpoints.add(endpoint.toAndroid(mParser));
+ outputEndpointCableCounts.add(getNumJacks(endpoint));
+ MidiEventMultiScheduler scheduler =
+ new MidiEventMultiScheduler(getNumJacks(endpoint));
+ midiEventMultiSchedulers.add(scheduler);
} else {
inputEndpoints.add(endpoint.toAndroid(mParser));
+ inputEndpointCableCounts.add(getNumJacks(endpoint));
}
}
if (!outputEndpoints.isEmpty() || !inputEndpoints.isEmpty()) {
@@ -341,40 +359,69 @@
mUsbDeviceConnections.add(connection);
mInputUsbEndpoints.add(inputEndpoints);
mOutputUsbEndpoints.add(outputEndpoints);
+ mInputUsbEndpointCableCounts.add(inputEndpointCableCounts);
+ mOutputUsbEndpointCableCounts.add(outputEndpointCableCounts);
+ mMidiEventMultiSchedulers.add(midiEventMultiSchedulers);
}
}
- mEventSchedulers = new MidiEventScheduler[mNumOutputs];
-
- for (int i = 0; i < mNumOutputs; i++) {
- MidiEventScheduler scheduler = new MidiEventScheduler();
- mEventSchedulers[i] = scheduler;
- mMidiInputPortReceivers[i].setReceiver(scheduler.getReceiver());
+ // Set up event schedulers
+ int outputIndex = 0;
+ for (int connectionIndex = 0; connectionIndex < mMidiEventMultiSchedulers.size();
+ connectionIndex++) {
+ for (int endpointIndex = 0;
+ endpointIndex < mMidiEventMultiSchedulers.get(connectionIndex).size();
+ endpointIndex++) {
+ int cableCount =
+ mOutputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex);
+ MidiEventMultiScheduler multiScheduler =
+ mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex);
+ for (int cableNumber = 0; cableNumber < cableCount; cableNumber++) {
+ MidiEventScheduler scheduler = multiScheduler.getEventScheduler(cableNumber);
+ mMidiInputPortReceivers[outputIndex].setReceiver(scheduler.getReceiver());
+ outputIndex++;
+ }
+ }
}
final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
// Create input thread for each input port of the physical device
- int portNumber = 0;
+ int portStartNumber = 0;
for (int connectionIndex = 0; connectionIndex < mInputUsbEndpoints.size();
connectionIndex++) {
for (int endpointIndex = 0;
endpointIndex < mInputUsbEndpoints.get(connectionIndex).size();
endpointIndex++) {
+ // Each USB endpoint maps to one or more outputReceivers. USB MIDI data from an
+ // endpoint should be sent to the appropriate outputReceiver. A new thread is
+ // created and waits for incoming USB data. Once the data is received, it is added
+ // to the packet converter. The packet converter acts as an inverse multiplexer.
+ // With a for loop, data is pulled per cable and sent to the correct output
+ // receiver. The first byte of each legacy MIDI 1.0 USB message indicates which
+ // cable the data should be used and is how the reverse multiplexer directs data.
+ // For MIDI UMP endpoints, a multiplexer is not needed as we are just swapping
+ // the endianness of the packets.
final UsbDeviceConnection connectionFinal =
mUsbDeviceConnections.get(connectionIndex);
final UsbEndpoint endpointFinal =
mInputUsbEndpoints.get(connectionIndex).get(endpointIndex);
- final int portFinal = portNumber;
+ final int portStartFinal = portStartNumber;
+ final int cableCountFinal =
+ mInputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex);
- Thread newThread = new Thread("UsbDirectMidiDevice input thread " + portFinal) {
+ Thread newThread = new Thread("UsbDirectMidiDevice input thread "
+ + portStartFinal) {
@Override
public void run() {
final UsbRequest request = new UsbRequest();
+ final UsbMidiPacketConverter packetConverter = new UsbMidiPacketConverter();
+ packetConverter.createDecoders(cableCountFinal);
try {
request.initialize(connectionFinal, endpointFinal);
byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()];
- while (true) {
+ boolean keepGoing = true;
+ while (keepGoing) {
if (Thread.currentThread().interrupted()) {
Log.w(TAG, "input thread interrupted");
break;
@@ -404,42 +451,56 @@
logByteArray("Input before conversion ", inputBuffer,
0, bytesRead);
}
+
+ // Add packets into the packet decoder.
+ if (!mIsUniversalMidiDevice) {
+ packetConverter.decodeMidiPackets(inputBuffer, bytesRead);
+ }
+
byte[] convertedArray;
- if (mIsUniversalMidiDevice) {
- // For USB, each 32 bit word of a UMP is
- // sent with the least significant byte first.
- convertedArray = swapEndiannessPerWord(inputBuffer,
- bytesRead);
- } else {
- if (mUsbMidiPacketConverter == null) {
- Log.w(TAG, "mUsbMidiPacketConverter is null");
+ for (int cableNumber = 0; cableNumber < cableCountFinal;
+ cableNumber++) {
+ if (mIsUniversalMidiDevice) {
+ // For USB, each 32 bit word of a UMP is
+ // sent with the least significant byte first.
+ convertedArray = swapEndiannessPerWord(inputBuffer,
+ bytesRead);
+ } else {
+ convertedArray =
+ packetConverter.pullDecodedMidiPackets(
+ cableNumber);
+ }
+
+ if (DEBUG) {
+ logByteArray("Input " + cableNumber
+ + " after conversion ", convertedArray, 0,
+ convertedArray.length);
+ }
+
+ if (convertedArray.length == 0) {
+ continue;
+ }
+
+ if ((outputReceivers == null)
+ || (outputReceivers[portStartFinal + cableNumber]
+ == null)) {
+ Log.w(TAG, "outputReceivers is null");
+ keepGoing = false;
break;
}
- convertedArray =
- mUsbMidiPacketConverter.usbMidiToRawMidi(
- inputBuffer, bytesRead);
- }
+ outputReceivers[portStartFinal + cableNumber].send(
+ convertedArray, 0, convertedArray.length,
+ timestamp);
- if (DEBUG) {
- logByteArray("Input after conversion ", convertedArray,
- 0, convertedArray.length);
- }
-
- if ((outputReceivers == null)
- || (outputReceivers[portFinal] == null)) {
- Log.w(TAG, "outputReceivers is null");
- break;
- }
- outputReceivers[portFinal].send(convertedArray, 0,
- convertedArray.length, timestamp);
-
- // Boost power if there seems to be a voice message.
- // For legacy devices, boost when message is more than size 1.
- // For UMP devices, boost for channel voice messages.
- if ((mPowerBoostSetter != null && convertedArray.length > 1)
- && (!mIsUniversalMidiDevice
- || isChannelVoiceMessage(convertedArray))) {
- mPowerBoostSetter.boostPower();
+ // Boost power if there seems to be a voice message.
+ // For legacy devices, boost if message length > 1.
+ // For UMP devices, boost for channel voice messages.
+ if ((mPowerBoostSetter != null
+ && convertedArray.length > 1)
+ && (!mIsUniversalMidiDevice
+ || isChannelVoiceMessage(convertedArray))) {
+ mPowerBoostSetter.boostPower();
+ }
}
}
}
@@ -455,64 +516,93 @@
};
newThread.start();
mThreads.add(newThread);
- portNumber++;
+ portStartNumber += cableCountFinal;
}
}
// Create output thread for each output port of the physical device
- portNumber = 0;
+ portStartNumber = 0;
for (int connectionIndex = 0; connectionIndex < mOutputUsbEndpoints.size();
connectionIndex++) {
for (int endpointIndex = 0;
endpointIndex < mOutputUsbEndpoints.get(connectionIndex).size();
endpointIndex++) {
+ // Each USB endpoint maps to one or more MIDI ports. Each port has an event
+ // scheduler that is used to pull incoming raw MIDI bytes from Android apps.
+ // With a MidiEventMultiScheduler, data can be pulled if any of the schedulers
+ // have new incoming data. This data is then packaged as USB MIDI packets before
+ // getting sent through USB. One thread will be created per endpoint that pulls
+ // data from all relevant event schedulers. Raw MIDI from the event schedulers
+ // will be converted to the correct USB MIDI format before getting sent through
+ // USB.
+
final UsbDeviceConnection connectionFinal =
mUsbDeviceConnections.get(connectionIndex);
final UsbEndpoint endpointFinal =
mOutputUsbEndpoints.get(connectionIndex).get(endpointIndex);
- final int portFinal = portNumber;
- final MidiEventScheduler eventSchedulerFinal = mEventSchedulers[portFinal];
+ final int portStartFinal = portStartNumber;
+ final int cableCountFinal =
+ mOutputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex);
+ final MidiEventMultiScheduler multiSchedulerFinal =
+ mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex);
- Thread newThread = new Thread("UsbDirectMidiDevice output thread " + portFinal) {
+ Thread newThread = new Thread("UsbDirectMidiDevice output write thread "
+ + portStartFinal) {
@Override
public void run() {
try {
- while (true) {
- if (Thread.currentThread().interrupted()) {
- Log.w(TAG, "output thread interrupted");
+ final ByteArrayOutputStream midi2ByteStream =
+ new ByteArrayOutputStream();
+ final UsbMidiPacketConverter packetConverter =
+ new UsbMidiPacketConverter();
+ packetConverter.createEncoders(cableCountFinal);
+ boolean isInterrupted = false;
+ while (!isInterrupted) {
+ boolean wasSuccessful = multiSchedulerFinal.waitNextEvent();
+ if (!wasSuccessful) {
+ Log.d(TAG, "output thread closed");
break;
}
- MidiEvent event;
- try {
- event = (MidiEvent) eventSchedulerFinal.waitNextEvent();
- } catch (InterruptedException e) {
- Log.w(TAG, "event scheduler interrupted");
- break;
- }
- if (event == null) {
- Log.w(TAG, "event is null");
- break;
- }
-
- if (DEBUG) {
- logByteArray("Output before conversion ", event.data, 0,
- event.count);
- }
-
- byte[] convertedArray;
- if (mIsUniversalMidiDevice) {
- // For USB, each 32 bit word of a UMP is
- // sent with the least significant byte first.
- convertedArray = swapEndiannessPerWord(event.data,
- event.count);
- } else {
- if (mUsbMidiPacketConverter == null) {
- Log.w(TAG, "mUsbMidiPacketConverter is null");
- break;
+ long now = System.nanoTime();
+ for (int cableNumber = 0; cableNumber < cableCountFinal;
+ cableNumber++) {
+ MidiEventScheduler eventScheduler =
+ multiSchedulerFinal.getEventScheduler(cableNumber);
+ MidiEvent event =
+ (MidiEvent) eventScheduler.getNextEvent(now);
+ while (event != null) {
+ if (DEBUG) {
+ logByteArray("Output before conversion ",
+ event.data, 0, event.count);
+ }
+ if (mIsUniversalMidiDevice) {
+ // For USB, each 32 bit word of a UMP is
+ // sent with the least significant byte first.
+ byte[] convertedArray = swapEndiannessPerWord(
+ event.data, event.count);
+ midi2ByteStream.write(convertedArray, 0,
+ convertedArray.length);
+ } else {
+ packetConverter.encodeMidiPackets(event.data,
+ event.count, cableNumber);
+ }
+ eventScheduler.addEventToPool(event);
+ event = (MidiEvent) eventScheduler.getNextEvent(now);
}
+ }
+
+ if (Thread.currentThread().interrupted()) {
+ Log.d(TAG, "output thread interrupted");
+ break;
+ }
+
+ byte[] convertedArray = new byte[0];
+ if (mIsUniversalMidiDevice) {
+ convertedArray = midi2ByteStream.toByteArray();
+ midi2ByteStream.reset();
+ } else {
convertedArray =
- mUsbMidiPacketConverter.rawMidiToUsbMidi(
- event.data, event.count, portFinal);
+ packetConverter.pullEncodedMidiPackets();
}
if (DEBUG) {
@@ -520,7 +610,6 @@
convertedArray.length);
}
- boolean isInterrupted = false;
// Split the packet into multiple if they are greater than the
// endpoint's max packet size.
for (int curPacketStart = 0;
@@ -558,11 +647,9 @@
}
}
}
- if (isInterrupted == true) {
- break;
- }
- eventSchedulerFinal.addEventToPool(event);
}
+ } catch (InterruptedException e) {
+ Log.w(TAG, "output thread: ", e);
} catch (NullPointerException e) {
Log.e(TAG, "output thread: ", e);
}
@@ -571,7 +658,7 @@
};
newThread.start();
mThreads.add(newThread);
- portNumber++;
+ portStartNumber += cableCountFinal;
}
}
@@ -667,11 +754,21 @@
}
mThreads = null;
- for (int i = 0; i < mEventSchedulers.length; i++) {
+ for (int i = 0; i < mMidiInputPortReceivers.length; i++) {
mMidiInputPortReceivers[i].setReceiver(null);
- mEventSchedulers[i].close();
}
- mEventSchedulers = null;
+
+ for (int connectionIndex = 0; connectionIndex < mMidiEventMultiSchedulers.size();
+ connectionIndex++) {
+ for (int endpointIndex = 0;
+ endpointIndex < mMidiEventMultiSchedulers.get(connectionIndex).size();
+ endpointIndex++) {
+ MidiEventMultiScheduler multiScheduler =
+ mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex);
+ multiScheduler.close();
+ }
+ }
+ mMidiEventMultiSchedulers = null;
for (UsbDeviceConnection connection : mUsbDeviceConnections) {
connection.close();
@@ -679,8 +776,8 @@
mUsbDeviceConnections = null;
mInputUsbEndpoints = null;
mOutputUsbEndpoints = null;
-
- mUsbMidiPacketConverter = null;
+ mInputUsbEndpointCableCounts = null;
+ mOutputUsbEndpointCableCounts = null;
mIsOpen = false;
}
@@ -788,4 +885,19 @@
return messageType == MESSAGE_TYPE_MIDI_1_CHANNEL_VOICE
|| messageType == MESSAGE_TYPE_MIDI_2_CHANNEL_VOICE;
}
+
+ // Returns the number of jacks for MIDI 1.0 endpoints.
+ // For MIDI 2.0 endpoints, this concept does not exist and each endpoint should be treated as
+ // one port.
+ private int getNumJacks(UsbEndpointDescriptor usbEndpointDescriptor) {
+ UsbDescriptor classSpecificEndpointDescriptor =
+ usbEndpointDescriptor.getClassSpecificEndpointDescriptor();
+ if (classSpecificEndpointDescriptor != null
+ && (classSpecificEndpointDescriptor instanceof UsbACMidi10Endpoint)) {
+ UsbACMidi10Endpoint midiEndpoint =
+ (UsbACMidi10Endpoint) classSpecificEndpointDescriptor;
+ return midiEndpoint.getNumJacks();
+ }
+ return 1;
+ }
}
diff --git a/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java b/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java
index 56bb236..65d7a41 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java
@@ -16,12 +16,17 @@
package com.android.server.usb;
+import android.util.Log;
+
import java.io.ByteArrayOutputStream;
/**
- * Converts between MIDI packets and USB MIDI 1.0 packets.
+ * Converts between raw MIDI packets and USB MIDI 1.0 packets.
+ * This is NOT thread-safe. Please handle locking outside this function for multiple threads.
+ * For data mapping to an invalid cable number, this converter will use the first cable.
*/
public class UsbMidiPacketConverter {
+ private static final String TAG = "UsbMidiPacketConverter";
// Refer to Table 4-1 in USB MIDI 1.0 spec.
private static final int[] PAYLOAD_SIZE = new int[]{
@@ -74,54 +79,133 @@
private static final byte SYSEX_START_EXCLUSIVE = (byte) 0xF0;
private static final byte SYSEX_END_EXCLUSIVE = (byte) 0xF7;
- private UsbMidiDecoder mUsbMidiDecoder = new UsbMidiDecoder();
private UsbMidiEncoder[] mUsbMidiEncoders;
+ private ByteArrayOutputStream mEncoderOutputStream = new ByteArrayOutputStream();
- public UsbMidiPacketConverter(int numEncoders) {
- mUsbMidiEncoders = new UsbMidiEncoder[numEncoders];
- for (int i = 0; i < numEncoders; i++) {
- mUsbMidiEncoders[i] = new UsbMidiEncoder();
- }
- }
+ private UsbMidiDecoder mUsbMidiDecoder;
/**
- * Converts a USB MIDI array into a raw MIDI array.
+ * Creates encoders.
*
- * @param usbMidiBytes the USB MIDI bytes to convert
- * @param size the size of usbMidiBytes
- * @return byte array of raw MIDI packets
+ * createEncoders() must be called before raw MIDI can be converted to USB MIDI.
+ *
+ * @param size the number of encoders to create
*/
- public byte[] usbMidiToRawMidi(byte[] usbMidiBytes, int size) {
- return mUsbMidiDecoder.decode(usbMidiBytes, size);
+ public void createEncoders(int size) {
+ mUsbMidiEncoders = new UsbMidiEncoder[size];
+ for (int i = 0; i < size; i++) {
+ mUsbMidiEncoders[i] = new UsbMidiEncoder(i);
+ }
}
/**
* Converts a raw MIDI array into a USB MIDI array.
*
+ * Call pullEncodedMidiPackets to retrieve the byte array.
+ *
* @param midiBytes the raw MIDI bytes to convert
* @param size the size of usbMidiBytes
* @param encoderId which encoder to use
+ */
+ public void encodeMidiPackets(byte[] midiBytes, int size, int encoderId) {
+ // Use the first encoder if the encoderId is invalid.
+ if (encoderId >= mUsbMidiEncoders.length) {
+ Log.w(TAG, "encoderId " + encoderId + " invalid");
+ encoderId = 0;
+ }
+ byte[] encodedPacket = mUsbMidiEncoders[encoderId].encode(midiBytes, size);
+ mEncoderOutputStream.write(encodedPacket, 0, encodedPacket.length);
+ }
+
+ /**
+ * Returns the encoded MIDI packets from encodeMidiPackets
+ *
* @return byte array of USB MIDI packets
*/
- public byte[] rawMidiToUsbMidi(byte[] midiBytes, int size, int encoderId) {
- return mUsbMidiEncoders[encoderId].encode(midiBytes, size);
+ public byte[] pullEncodedMidiPackets() {
+ byte[] output = mEncoderOutputStream.toByteArray();
+ mEncoderOutputStream.reset();
+ return output;
+ }
+
+ /**
+ * Creates decoders.
+ *
+ * createDecoders() must be called before USB MIDI can be converted to raw MIDI.
+ *
+ * @param size the number of decoders to create
+ */
+ public void createDecoders(int size) {
+ mUsbMidiDecoder = new UsbMidiDecoder(size);
+ }
+
+ /**
+ * Converts a USB MIDI array into a multiple MIDI arrays, one per cable.
+ *
+ * Call pullDecodedMidiPackets to retrieve the byte array.
+ *
+ * @param usbMidiBytes the USB MIDI bytes to convert
+ * @param size the size of usbMidiBytes
+ */
+ public void decodeMidiPackets(byte[] usbMidiBytes, int size) {
+ mUsbMidiDecoder.decode(usbMidiBytes, size);
+ }
+
+ /**
+ * Returns the decoded MIDI packets from decodeMidiPackets
+ *
+ * @param cableNumber the cable to pull data from
+ * @return byte array of raw MIDI packets
+ */
+ public byte[] pullDecodedMidiPackets(int cableNumber) {
+ return mUsbMidiDecoder.pullBytes(cableNumber);
}
private class UsbMidiDecoder {
+ int mNumJacks;
+ ByteArrayOutputStream[] mDecodedByteArrays;
+
+ UsbMidiDecoder(int numJacks) {
+ mNumJacks = numJacks;
+ mDecodedByteArrays = new ByteArrayOutputStream[numJacks];
+ for (int i = 0; i < numJacks; i++) {
+ mDecodedByteArrays[i] = new ByteArrayOutputStream();
+ }
+ }
+
// Decodes the data from USB MIDI to raw MIDI.
// Each valid 4 byte input maps to a 1-3 byte output.
// Reference the USB MIDI 1.0 spec for more info.
- public byte[] decode(byte[] usbMidiBytes, int size) {
+ public void decode(byte[] usbMidiBytes, int size) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ if (size % 4 != 0) {
+ Log.w(TAG, "size " + size + " not multiple of 4");
+ }
for (int i = 0; i + 3 < size; i += 4) {
+ int cableNumber = (usbMidiBytes[i] >> 4) & 0x0f;
int codeIndex = usbMidiBytes[i] & 0x0f;
int numPayloadBytes = PAYLOAD_SIZE[codeIndex];
if (numPayloadBytes < 0) {
continue;
}
- outputStream.write(usbMidiBytes, i + 1, numPayloadBytes);
+ // Use the first cable if the cable number is invalid.
+ if (cableNumber >= mNumJacks) {
+ Log.w(TAG, "cableNumber " + cableNumber + " invalid");
+ cableNumber = 0;
+ }
+ mDecodedByteArrays[cableNumber].write(usbMidiBytes, i + 1, numPayloadBytes);
}
- return outputStream.toByteArray();
+ }
+
+ public byte[] pullBytes(int cableNumber) {
+ // Use the first cable if the cable number is invalid.
+ if (cableNumber >= mNumJacks) {
+ Log.w(TAG, "cableNumber " + cableNumber + " invalid");
+ cableNumber = 0;
+ }
+ byte[] output = mDecodedByteArrays[cableNumber].toByteArray();
+ mDecodedByteArrays[cableNumber].reset();
+ return output;
}
}
@@ -135,6 +219,13 @@
private byte[] mEmptyBytes = new byte[3]; // Used to fill out extra data
+ private byte mShiftedCableNumber;
+
+ UsbMidiEncoder(int cableNumber) {
+ // Jack Id is always the left nibble of every byte so shift this now.
+ mShiftedCableNumber = (byte) (cableNumber << 4);
+ }
+
// Encodes the data from raw MIDI to USB MIDI.
// Each valid 1-3 byte input maps to a 4 byte output.
// Reference the USB MIDI 1.0 spec for more info.
@@ -153,7 +244,8 @@
midiBytes[curLocation];
mNumStoredSystemExclusiveBytes++;
if (mNumStoredSystemExclusiveBytes == 3) {
- outputStream.write(CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES);
+ outputStream.write(CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES
+ | mShiftedCableNumber);
outputStream.write(mStoredSystemExclusiveBytes, 0, 3);
mNumStoredSystemExclusiveBytes = 0;
}
@@ -179,7 +271,7 @@
byte codeIndexNumber = (byte) ((midiBytes[curLocation] >> 4) & 0x0f);
int channelMessageSize = PAYLOAD_SIZE[codeIndexNumber];
if (curLocation + channelMessageSize <= size) {
- outputStream.write(codeIndexNumber);
+ outputStream.write(codeIndexNumber | mShiftedCableNumber);
outputStream.write(midiBytes, curLocation, channelMessageSize);
// Fill in the rest of the bytes with 0.
outputStream.write(mEmptyBytes, 0, 3 - channelMessageSize);
@@ -197,8 +289,8 @@
curLocation++;
} else if (midiBytes[curLocation] == SYSEX_END_EXCLUSIVE) {
// 1 byte is 0x05, 2 bytes is 0x06, and 3 bytes is 0x07
- outputStream.write(CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE
- + mNumStoredSystemExclusiveBytes);
+ outputStream.write((CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE
+ + mNumStoredSystemExclusiveBytes) | mShiftedCableNumber);
mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
midiBytes[curLocation];
mNumStoredSystemExclusiveBytes++;
@@ -218,7 +310,7 @@
} else {
int systemMessageSize = PAYLOAD_SIZE[codeIndexNumber];
if (curLocation + systemMessageSize <= size) {
- outputStream.write(codeIndexNumber);
+ outputStream.write(codeIndexNumber | mShiftedCableNumber);
outputStream.write(midiBytes, curLocation, systemMessageSize);
// Fill in the rest of the bytes with 0.
outputStream.write(mEmptyBytes, 0, 3 - systemMessageSize);
@@ -236,7 +328,7 @@
}
private void writeSingleByte(ByteArrayOutputStream outputStream, byte byteToWrite) {
- outputStream.write(CODE_INDEX_NUMBER_SINGLE_BYTE);
+ outputStream.write(CODE_INDEX_NUMBER_SINGLE_BYTE | mShiftedCableNumber);
outputStream.write(byteToWrite);
outputStream.write(0);
outputStream.write(0);
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
index 1f448ac..117a3d9 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -118,7 +118,7 @@
mClassSpecificEndpointDescriptor = descriptor;
}
- UsbDescriptor getClassSpecificEndpointDescriptor() {
+ public UsbDescriptor getClassSpecificEndpointDescriptor() {
return mClassSpecificEndpointDescriptor;
}
diff --git a/tests/MidiTests/Android.bp b/tests/MidiTests/Android.bp
new file mode 100644
index 0000000..254770d
--- /dev/null
+++ b/tests/MidiTests/Android.bp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "MidiTests",
+ srcs: ["**/*.java"],
+ static_libs: [
+ "androidx.test.rules",
+ "mockito-target-inline-minus-junit4",
+ "platform-test-annotations",
+ "services.midi",
+ "truth-prebuilt",
+ ],
+ jni_libs: ["libdexmakerjvmtiagent"],
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: ["device-tests"],
+}
diff --git a/tests/MidiTests/AndroidManifest.xml b/tests/MidiTests/AndroidManifest.xml
new file mode 100644
index 0000000..0ee1b449
--- /dev/null
+++ b/tests/MidiTests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.midi" >
+
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.midi"
+ android:label="MidiTests"/>
+</manifest>
diff --git a/tests/MidiTests/AndroidTest.xml b/tests/MidiTests/AndroidTest.xml
new file mode 100644
index 0000000..9320f0a
--- /dev/null
+++ b/tests/MidiTests/AndroidTest.xml
@@ -0,0 +1,30 @@
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs sample instrumentation test.">
+ <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="MidiTests.apk"/>
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-tag" value="MidiTests"/>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.server.midi"/>
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/tests/MidiTests/OWNERS b/tests/MidiTests/OWNERS
new file mode 100644
index 0000000..af273a6
--- /dev/null
+++ b/tests/MidiTests/OWNERS
@@ -0,0 +1 @@
+include /services/midi/OWNERS
diff --git a/tests/MidiTests/TEST_MAPPING b/tests/MidiTests/TEST_MAPPING
new file mode 100644
index 0000000..60416a8
--- /dev/null
+++ b/tests/MidiTests/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "MidiTests"
+ }
+ ]
+}
diff --git a/tests/MidiTests/src/com/android/server/midi/MidiEventMultiSchedulerTest.java b/tests/MidiTests/src/com/android/server/midi/MidiEventMultiSchedulerTest.java
new file mode 100644
index 0000000..1659cc0
--- /dev/null
+++ b/tests/MidiTests/src/com/android/server/midi/MidiEventMultiSchedulerTest.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.midi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.midi.MidiEventMultiScheduler;
+import com.android.internal.midi.MidiEventScheduler;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+
+/**
+ * Unit tests for com.android.internal.midi.MidiEventMultiScheduler.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MidiEventMultiSchedulerTest {
+ private byte[] generateRandomByteStream(Random rnd, int size) {
+ byte[] output = new byte[size];
+ rnd.nextBytes(output);
+ return output;
+ }
+
+ private void compareByteArrays(byte[] expectedArray, byte[] outputArray) {
+ assertEquals(expectedArray.length, outputArray.length);
+ for (int i = 0; i < outputArray.length; i++) {
+ assertEquals(expectedArray[i], outputArray[i]);
+ }
+ }
+
+ private long timeFromNow(long milliseconds) {
+ return System.nanoTime() + 1000000L * milliseconds;
+ }
+
+ @Test
+ public void testMultiScheduler() {
+ try {
+ MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3);
+ assertEquals(3, multiScheduler.getNumEventSchedulers());
+ MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0);
+ MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1);
+ MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2);
+
+ scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf0, (byte) 0xf7},
+ 0, 2, timeFromNow(100)));
+ scheduler1.add(scheduler1.createScheduledEvent(new byte[]{(byte) 0xf1, (byte) 0xf2},
+ 0, 2, timeFromNow(200)));
+ scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf3, (byte) 0xf4},
+ 0, 2, timeFromNow(300)));
+ scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf5, (byte) 0xf6},
+ 0, 2, timeFromNow(400)));
+ assertTrue(multiScheduler.waitNextEvent());
+ assertNotNull(scheduler0.getNextEvent(System.nanoTime()));
+ assertNull(scheduler1.getNextEvent(System.nanoTime()));
+ assertNull(scheduler2.getNextEvent(System.nanoTime()));
+ assertTrue(multiScheduler.waitNextEvent());
+ assertNull(scheduler0.getNextEvent(System.nanoTime()));
+ assertNotNull(scheduler1.getNextEvent(System.nanoTime()));
+ assertNull(scheduler2.getNextEvent(System.nanoTime()));
+ assertTrue(multiScheduler.waitNextEvent());
+ assertNull(scheduler0.getNextEvent(System.nanoTime()));
+ assertNull(scheduler1.getNextEvent(System.nanoTime()));
+ assertNotNull(scheduler2.getNextEvent(System.nanoTime()));
+ assertTrue(multiScheduler.waitNextEvent());
+ assertNotNull(scheduler0.getNextEvent(System.nanoTime()));
+ assertNull(scheduler1.getNextEvent(System.nanoTime()));
+ assertNull(scheduler2.getNextEvent(System.nanoTime()));
+ } catch (InterruptedException ex) {
+
+ }
+ }
+
+ @Test
+ public void testSchedulerLargeData() {
+ try {
+ MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1);
+ assertEquals(1, multiScheduler.getNumEventSchedulers());
+ MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0);
+
+ Random rnd = new Random(42);
+
+ final int arraySize = 1000;
+ byte[] expectedArray = generateRandomByteStream(rnd, arraySize);
+
+ scheduler0.add(scheduler0.createScheduledEvent(expectedArray, 0, arraySize,
+ timeFromNow(100)));
+ assertTrue(multiScheduler.waitNextEvent());
+ MidiEventScheduler.MidiEvent event =
+ (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime());
+ assertNotNull(event);
+ compareByteArrays(expectedArray, event.data);
+ } catch (InterruptedException ex) {
+
+ }
+ }
+
+ @Test
+ public void testSchedulerClose() {
+ try {
+ MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1);
+ assertEquals(1, multiScheduler.getNumEventSchedulers());
+ MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0);
+ scheduler0.close();
+ // After all schedulers are closed, waitNextEvent() should return false.
+ assertFalse(multiScheduler.waitNextEvent());
+ } catch (InterruptedException ex) {
+
+ }
+ }
+
+ @Test
+ public void testSchedulerMultiClose() {
+ try {
+ MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3);
+ assertEquals(3, multiScheduler.getNumEventSchedulers());
+ multiScheduler.close();
+ // After all schedulers are closed, waitNextEvent() should return false.
+ assertFalse(multiScheduler.waitNextEvent());
+ } catch (InterruptedException ex) {
+
+ }
+ }
+
+ @Test
+ public void testSchedulerNoPreemptiveClose() {
+ try {
+ MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3);
+ assertEquals(3, multiScheduler.getNumEventSchedulers());
+ MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0);
+ MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1);
+ MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2);
+ scheduler0.close();
+ scheduler1.close();
+ scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf5, (byte) 0xf6},
+ 0, 2, timeFromNow(100)));
+ assertTrue(multiScheduler.waitNextEvent());
+ scheduler2.close();
+ // After all schedulers are closed, waitNextEvent() should return false.
+ assertFalse(multiScheduler.waitNextEvent());
+ } catch (InterruptedException ex) {
+
+ }
+ }
+
+ @Test
+ public void testSchedulerSpamEvents() {
+ MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1);
+ assertEquals(1, multiScheduler.getNumEventSchedulers());
+ MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0);
+ // Create a msg with size 1
+ byte[] msg = new byte[1];
+ for (int i = 0; i < 1000; i++) {
+ msg[0] = (byte) i;
+ scheduler0.add(scheduler0.createScheduledEvent(msg, 0, 1, timeFromNow(0)));
+ MidiEventScheduler.MidiEvent event =
+ (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime());
+ assertNotNull(event);
+ assertEquals(1, event.count);
+ assertEquals(msg[0], event.data[0]);
+ }
+ assertNull(scheduler0.getNextEvent(System.nanoTime()));
+ }
+
+ @Test
+ public void testSchedulerSpamEventsPullLater() {
+ MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1);
+ assertEquals(1, multiScheduler.getNumEventSchedulers());
+ MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0);
+ // Create a msg with size 1
+ byte[] msg = new byte[1];
+ for (int i = 0; i < 1000; i++) {
+ msg[0] = (byte) i;
+ scheduler0.add(scheduler0.createScheduledEvent(msg, 0, 1, timeFromNow(0)));
+ }
+
+ for (int i = 0; i < 1000; i++) {
+ MidiEventScheduler.MidiEvent event =
+ (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime());
+ assertNotNull(event);
+ assertEquals(1, event.count);
+ assertEquals((byte) i, event.data[0]);
+ }
+ assertNull(scheduler0.getNextEvent(System.nanoTime()));
+ }
+
+ @Test
+ public void testSchedulerSpamEventsCallbackLater() {
+ MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1);
+ assertEquals(1, multiScheduler.getNumEventSchedulers());
+ MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0);
+ // Create a msg with size 1
+ byte[] msg = new byte[1];
+ for (int i = 0; i < 1000; i++) {
+ msg[0] = (byte) i;
+ scheduler0.add(scheduler0.createScheduledEvent(msg, 0, 1, timeFromNow(0)));
+ }
+
+ for (int i = 0; i < 1000; i++) {
+ try {
+ assertTrue(multiScheduler.waitNextEvent());
+ } catch (InterruptedException ex) {
+ }
+ MidiEventScheduler.MidiEvent event =
+ (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime());
+ assertNotNull(event);
+ assertEquals(1, event.count);
+ assertEquals((byte) i, event.data[0]);
+ }
+ assertNull(scheduler0.getNextEvent(System.nanoTime()));
+ }
+
+ @Test
+ public void testMultiSchedulerOutOfOrder() {
+ try {
+ MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3);
+ assertEquals(3, multiScheduler.getNumEventSchedulers());
+ MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0);
+ MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1);
+ MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2);
+
+ scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf3},
+ 0, 1,
+ timeFromNow(400)));
+ scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf2},
+ 0, 1,
+ timeFromNow(300)));
+ scheduler1.add(scheduler1.createScheduledEvent(new byte[]{(byte) 0xf1},
+ 0, 1,
+ timeFromNow(200)));
+ scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf0},
+ 0, 1,
+ timeFromNow(100)));
+
+ assertTrue(multiScheduler.waitNextEvent());
+ MidiEventScheduler.MidiEvent event =
+ (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime());
+ assertNotNull(event);
+ assertEquals(1, event.count);
+ assertEquals((byte) 0xf0, event.data[0]);
+ assertNull(scheduler1.getNextEvent(System.nanoTime()));
+ assertNull(scheduler2.getNextEvent(System.nanoTime()));
+ assertTrue(multiScheduler.waitNextEvent());
+ assertNull(scheduler0.getNextEvent(System.nanoTime()));
+ event = (MidiEventScheduler.MidiEvent) scheduler1.getNextEvent(System.nanoTime());
+ assertNotNull(event);
+ assertEquals(1, event.count);
+ assertEquals((byte) 0xf1, event.data[0]);
+ assertNull(scheduler2.getNextEvent(System.nanoTime()));
+ assertTrue(multiScheduler.waitNextEvent());
+ assertNull(scheduler0.getNextEvent(System.nanoTime()));
+ assertNull(scheduler1.getNextEvent(System.nanoTime()));
+ event = (MidiEventScheduler.MidiEvent) scheduler2.getNextEvent(System.nanoTime());
+ assertNotNull(event);
+ assertEquals(1, event.count);
+ assertEquals((byte) 0xf2, event.data[0]);
+ assertTrue(multiScheduler.waitNextEvent());
+ event = (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime());
+ assertNotNull(event);
+ assertEquals(1, event.count);
+ assertEquals((byte) 0xf3, event.data[0]);
+ assertNull(scheduler1.getNextEvent(System.nanoTime()));
+ assertNull(scheduler2.getNextEvent(System.nanoTime()));
+ } catch (InterruptedException ex) {
+
+ }
+ }
+
+ @Test
+ public void testMultiSchedulerOutOfOrderNegativeTime() {
+ try {
+ MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3);
+ assertEquals(3, multiScheduler.getNumEventSchedulers());
+ MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0);
+ MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1);
+ MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2);
+
+ scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf3},
+ 0, 1,
+ timeFromNow(-100)));
+ scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf2},
+ 0, 1,
+ timeFromNow(-200)));
+ scheduler1.add(scheduler1.createScheduledEvent(new byte[]{(byte) 0xf1},
+ 0, 1,
+ timeFromNow(-300)));
+ scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf0},
+ 0, 1,
+ timeFromNow(-400)));
+
+ assertTrue(multiScheduler.waitNextEvent());
+ MidiEventScheduler.MidiEvent event =
+ (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime());
+ assertNotNull(event);
+ assertEquals(1, event.count);
+ assertEquals((byte) 0xf0, event.data[0]);
+ assertTrue(multiScheduler.waitNextEvent());
+ event = (MidiEventScheduler.MidiEvent) scheduler1.getNextEvent(System.nanoTime());
+ assertNotNull(event);
+ assertEquals(1, event.count);
+ assertEquals((byte) 0xf1, event.data[0]);
+ assertTrue(multiScheduler.waitNextEvent());
+ event = (MidiEventScheduler.MidiEvent) scheduler2.getNextEvent(System.nanoTime());
+ assertNotNull(event);
+ assertEquals(1, event.count);
+ assertEquals((byte) 0xf2, event.data[0]);
+ assertNull(scheduler1.getNextEvent(System.nanoTime()));
+ assertTrue(multiScheduler.waitNextEvent());
+ event = (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime());
+ assertNotNull(event);
+ assertEquals(1, event.count);
+ assertEquals((byte) 0xf3, event.data[0]);
+ assertNull(scheduler1.getNextEvent(System.nanoTime()));
+ assertNull(scheduler2.getNextEvent(System.nanoTime()));
+ } catch (InterruptedException ex) {
+
+ }
+ }
+}
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbMidiPacketConverterTest.java b/tests/UsbTests/src/com/android/server/usb/UsbMidiPacketConverterTest.java
new file mode 100644
index 0000000..ad701e5
--- /dev/null
+++ b/tests/UsbTests/src/com/android/server/usb/UsbMidiPacketConverterTest.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * Unit tests for com.android.server.usb.UsbMidiPacketConverter.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UsbMidiPacketConverterTest {
+ private byte[] generateRandomByteStream(Random rnd, int size) {
+ byte[] output = new byte[size];
+ rnd.nextBytes(output);
+ return output;
+ }
+
+ private void compareByteArrays(byte[] expectedArray, byte[] outputArray) {
+ assertEquals(expectedArray.length, outputArray.length);
+ for (int i = 0; i < outputArray.length; i++) {
+ assertEquals(expectedArray[i], outputArray[i]);
+ }
+ }
+
+ @Test
+ public void testDecoderSinglePacket() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createDecoders(2);
+ byte[] input = new byte[] {0x19 /* Cable 1 Note-On */, (byte) 0x91, 0x33, 0x66};
+ byte[] expectedOutputCable0 = new byte[] {};
+ byte[] expectedOutputCable1 = new byte[] {(byte) 0x91, 0x33, 0x66};
+ usbMidiPacketConverter.decodeMidiPackets(input, input.length);
+ byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
+ byte[] actualOutputCable1 = usbMidiPacketConverter.pullDecodedMidiPackets(1);
+ compareByteArrays(expectedOutputCable0, actualOutputCable0);
+ compareByteArrays(expectedOutputCable1, actualOutputCable1);
+ }
+
+ @Test
+ public void testDecoderMultiplePackets() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createDecoders(4);
+ byte[] input = new byte[] {
+ 0x1B /* Cable 1 Control Change */, (byte) 0xB4, 0x55, 0x6E,
+ 0x35 /* Cable 3 Single byte SysEx */, (byte) 0xF8, 0x00, 0x00,
+ 0x02 /* Cable 0 Two byte System Common */, (byte) 0xF3, 0x12, 0x00};
+ byte[] expectedOutputCable0 = new byte[] {(byte) 0xF3, 0x12};
+ byte[] expectedOutputCable1 = new byte[] {(byte) 0xB4, 0x55, 0x6E};
+ byte[] expectedOutputCable2 = new byte[] {};
+ byte[] expectedOutputCable3 = new byte[] {(byte) 0xF8};
+ usbMidiPacketConverter.decodeMidiPackets(input, input.length);
+ byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
+ byte[] actualOutputCable1 = usbMidiPacketConverter.pullDecodedMidiPackets(1);
+ byte[] actualOutputCable2 = usbMidiPacketConverter.pullDecodedMidiPackets(2);
+ byte[] actualOutputCable3 = usbMidiPacketConverter.pullDecodedMidiPackets(3);
+ compareByteArrays(expectedOutputCable0, actualOutputCable0);
+ compareByteArrays(expectedOutputCable1, actualOutputCable1);
+ compareByteArrays(expectedOutputCable2, actualOutputCable2);
+ compareByteArrays(expectedOutputCable3, actualOutputCable3);
+ }
+
+ @Test
+ public void testDecoderSysExEndFirstByte() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createDecoders(2);
+ byte[] input = new byte[] {
+ 0x14 /* Cable 1 SysEx Start */, (byte) 0xF0, 0x00, 0x01,
+ 0x15 /* Cable 1 Single byte SysEx End */, (byte) 0xF7, 0x00, 0x00};
+ byte[] expectedOutputCable0 = new byte[] {};
+ byte[] expectedOutputCable1 = new byte[] {
+ (byte) 0xF0, 0x00, 0x01,
+ (byte) 0xF7};
+ usbMidiPacketConverter.decodeMidiPackets(input, input.length);
+ byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
+ byte[] actualOutputCable1 = usbMidiPacketConverter.pullDecodedMidiPackets(1);
+ compareByteArrays(expectedOutputCable0, actualOutputCable0);
+ compareByteArrays(expectedOutputCable1, actualOutputCable1);
+ }
+
+ @Test
+ public void testDecoderSysExEndSecondByte() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createDecoders(1);
+ byte[] input = new byte[] {
+ 0x04 /* Cable 0 SysEx Start */, (byte) 0xF0, 0x00, 0x01,
+ 0x06 /* Cable 0 Two byte SysEx End */, 0x02, (byte) 0xF7, 0x00};
+ byte[] expectedOutputCable0 = new byte[] {
+ (byte) 0xF0, 0x00, 0x01,
+ 0x02, (byte) 0xF7};
+ usbMidiPacketConverter.decodeMidiPackets(input, input.length);
+ byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
+ compareByteArrays(expectedOutputCable0, actualOutputCable0);
+ }
+
+ @Test
+ public void testDecoderSysExEndThirdByte() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ byte[] input = new byte[] {
+ 0x04 /* Cable 0 SysEx Start */, (byte) 0xF0, 0x00, 0x01,
+ 0x07 /* Cable 0 Three byte SysEx End */, 0x02, 0x03, (byte) 0xF7};
+ usbMidiPacketConverter.createDecoders(1);
+ byte[] expectedOutputCable0 = new byte[] {
+ (byte) 0xF0, 0x00, 0x01,
+ 0x02, 0x03, (byte) 0xF7};
+ usbMidiPacketConverter.decodeMidiPackets(input, input.length);
+ byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
+ compareByteArrays(expectedOutputCable0, actualOutputCable0);
+ }
+
+ @Test
+ public void testDecoderSysExStartEnd() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ byte[] input = new byte[] {
+ 0x06 /* Cable 0 Two byte SysEx End */, (byte) 0xF0, (byte) 0xF7, 0x00};
+ usbMidiPacketConverter.createDecoders(1);
+ byte[] expectedOutputCable0 = new byte[] {
+ (byte) 0xF0, (byte) 0xF7};
+ usbMidiPacketConverter.decodeMidiPackets(input, input.length);
+ byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
+ compareByteArrays(expectedOutputCable0, actualOutputCable0);
+ }
+
+ @Test
+ public void testDecoderSysExStartByteEnd() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ byte[] input = new byte[] {
+ 0x07 /* Cable 0 Three byte SysEx End */, (byte) 0xF0, 0x44, (byte) 0xF7};
+ usbMidiPacketConverter.createDecoders(1);
+ byte[] expectedOutputCable0 = new byte[] {
+ (byte) 0xF0, 0x44, (byte) 0xF7};
+ usbMidiPacketConverter.decodeMidiPackets(input, input.length);
+ byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
+ compareByteArrays(expectedOutputCable0, actualOutputCable0);
+ }
+
+ @Test
+ public void testDecoderDefaultToFirstCable() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ byte[] input = new byte[] {0x49 /* Cable 4 Note-On */, (byte) 0x91, 0x22, 0x33};
+ usbMidiPacketConverter.createDecoders(1);
+ byte[] expectedOutputCable0 = new byte[] {
+ (byte) 0x91, 0x22, 0x33};
+ usbMidiPacketConverter.decodeMidiPackets(input, input.length);
+ byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
+ compareByteArrays(expectedOutputCable0, actualOutputCable0);
+ }
+
+ @Test
+ public void testDecoderLargePacketDoesNotCrash() {
+ for (long seed = 1001; seed < 5000; seed += 777) {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createDecoders(3);
+ Random rnd = new Random(seed);
+ byte[] input = generateRandomByteStream(rnd, 1003 /* arbitrary large size */);
+ usbMidiPacketConverter.decodeMidiPackets(input, input.length);
+ usbMidiPacketConverter.pullDecodedMidiPackets(0);
+ usbMidiPacketConverter.pullDecodedMidiPackets(1);
+ usbMidiPacketConverter.pullDecodedMidiPackets(2);
+ }
+ }
+
+ @Test
+ public void testEncoderBasic() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createEncoders(1);
+ byte[] input = new byte[] {(byte) 0x91 /* Note-On */, 0x33, 0x66};
+ byte[] expectedOutput = new byte[] {
+ 0x09 /* Cable 0 Note-On */, (byte) 0x91, 0x33, 0x66};
+ usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
+ byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
+ compareByteArrays(expectedOutput, output);
+ }
+
+ @Test
+ public void testEncoderMultiplePackets() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createEncoders(3);
+ byte[] inputCable2 = new byte[] {
+ (byte) 0xB4 /* Control Change */, 0x55, 0x6E};
+ byte[] inputCable1 = new byte[] {
+ (byte) 0xF8 /* Timing Clock (Single Byte) */,
+ (byte) 0xF3 /* Song Select (Two Bytes) */, 0x12};
+ byte[] expectedOutput = new byte[] {
+ 0x2B /* Cable 2 Control Change */, (byte) 0xB4, 0x55, 0x6E,
+ 0x15 /* Cable 1 Timing Clock */, (byte) 0xF8, 0x00, 0x00,
+ 0x12 /* Cable 1 Two Byte System Common */, (byte) 0xF3, 0x12, 0x00};
+ usbMidiPacketConverter.encodeMidiPackets(inputCable2, inputCable2.length, 2);
+ usbMidiPacketConverter.encodeMidiPackets(inputCable1, inputCable1.length, 1);
+ byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
+ compareByteArrays(expectedOutput, output);
+ }
+
+ @Test
+ public void testEncoderWeavePackets() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createEncoders(2);
+ byte[] inputCable1Msg1 = new byte[] {
+ (byte) 0x93 /* Note-On */, 0x23, 0x43};
+ byte[] inputCable0Msg = new byte[] {
+ (byte) 0xB4 /* Control Change */, 0x65, 0x26};
+ byte[] inputCable1Msg2 = new byte[] {
+ (byte) 0xA4 /* Poly-KeyPress */, 0x52, 0x76};
+ byte[] expectedOutput = new byte[] {
+ 0x19 /* Cable 1 Note-On */, (byte) 0x93, 0x23, 0x43,
+ 0x0B /* Cable 0 Control Change */, (byte) 0xB4, 0x65, 0x26,
+ 0x1A /* Cable 1 Poly-KeyPress */, (byte) 0xA4, 0x52, 0x76};
+ usbMidiPacketConverter.encodeMidiPackets(inputCable1Msg1, inputCable1Msg1.length, 1);
+ usbMidiPacketConverter.encodeMidiPackets(inputCable0Msg, inputCable0Msg.length, 0);
+ usbMidiPacketConverter.encodeMidiPackets(inputCable1Msg2, inputCable1Msg2.length, 1);
+ byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
+ compareByteArrays(expectedOutput, output);
+ }
+
+ @Test
+ public void testEncoderSysExEndFirstByte() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createEncoders(1);
+ byte[] input = new byte[] {
+ (byte) 0xF0 /* SysEx Start */, 0x00, 0x01,
+ (byte) 0xF7 /* SysEx End */};
+ byte[] expectedOutput = new byte[] {
+ 0x04 /* Cable 0 Three Byte SysEx Start */, (byte) 0xF0, 0x00, 0x01,
+ 0x05 /* Cable 0 One Byte SysEx End */, (byte) 0xF7, 0x00, 0x00};
+ usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
+ byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
+ compareByteArrays(expectedOutput, output);
+ }
+
+ @Test
+ public void testEncoderSysExEndSecondByte() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createEncoders(1);
+ byte[] input = new byte[] {
+ (byte) 0xF0 /* SysEx Start */, 0x00, 0x01,
+ 0x02, (byte) 0xF7 /* SysEx End */};
+ byte[] expectedOutput = new byte[] {
+ 0x04 /* Cable 0 Three Byte SysEx Start */, (byte) 0xF0, 0x00, 0x01,
+ 0x06 /* Cable 0 Two Byte SysEx End */, 0x02, (byte) 0xF7, 0x00};
+ usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
+ byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
+ compareByteArrays(expectedOutput, output);
+ }
+
+ @Test
+ public void testEncoderSysExEndThirdByte() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createEncoders(1);
+ byte[] input = new byte[] {
+ (byte) 0xF0 /* SysEx Start */, 0x00, 0x01,
+ 0x02, 0x03, (byte) 0xF7 /* SysEx End */};
+ byte[] expectedOutput = new byte[] {
+ 0x04 /* Cable 0 Three Byte SysEx Start */, (byte) 0xF0, 0x00, 0x01,
+ 0x07 /* Cable 0 Three Byte SysEx End */, 0x02, 0x03, (byte) 0xF7};
+ usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
+ byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
+ compareByteArrays(expectedOutput, output);
+ }
+
+ @Test
+ public void testEncoderSysExStartEnd() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createEncoders(1);
+ byte[] input = new byte[] {
+ (byte) 0xF0 /* SysEx Start */, (byte) 0xF7 /* SysEx End */};
+ byte[] expectedOutput = new byte[] {
+ 0x06 /* Cable 0 Two Byte SysEx End */, (byte) 0xF0, (byte) 0xF7, 0x00};
+ usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
+ byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
+ compareByteArrays(expectedOutput, output);
+ }
+
+ @Test
+ public void testEncoderSysExStartByteEnd() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createEncoders(1);
+ byte[] input = new byte[] {
+ (byte) 0xF0 /* SysEx Start */, 0x44, (byte) 0xF7 /* SysEx End */};
+ byte[] expectedOutput = new byte[] {
+ 0x07 /* Cable 0 Three Byte SysEx End */, (byte) 0xF0, 0x44, (byte) 0xF7};
+ usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
+ byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
+ compareByteArrays(expectedOutput, output);
+ }
+
+ @Test
+ public void testEncoderMultiplePulls() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createEncoders(1);
+
+ byte[] input = new byte[] {
+ (byte) 0xF0 /* SysEx Start */, 0x44, 0x55,
+ 0x66, 0x77}; // 0x66 and 0x77 will not be pulled the first time
+ byte[] expectedOutput = new byte[] {
+ 0x04 /* SysEx Start */, (byte) 0xF0, 0x44, 0x55};
+ usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
+ byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
+ compareByteArrays(expectedOutput, output);
+
+ input = new byte[] {
+ 0x11, // Combined with 0x66 and 0x77 above
+ 0x22, (byte) 0xF7 /* SysEx End */};
+ expectedOutput = new byte[] {
+ 0x04 /* Cable 0 SysEx Continue */, 0x66, 0x77, 0x11,
+ 0x06 /* Cable 0 Two Byte SysEx End */, 0x22, (byte) 0xF7, 0x00};
+ usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
+ output = usbMidiPacketConverter.pullEncodedMidiPackets();
+ compareByteArrays(expectedOutput, output);
+
+ input = new byte[] {
+ (byte) 0xF0 /* SysEx Start */, (byte) 0xF7 /* SysEx End */};
+ expectedOutput = new byte[] {
+ 0x06 /* Cable 0 Two Byte SysEx End */, (byte) 0xF0, (byte) 0xF7, 0x00};
+ usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
+ output = usbMidiPacketConverter.pullEncodedMidiPackets();
+ compareByteArrays(expectedOutput, output);
+ }
+
+ @Test
+ public void testEncoderDefaultToFirstCable() {
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createEncoders(2);
+ byte[] input = new byte[] {(byte) 0x91 /* Note-On */, 0x22, 0x33};
+ byte[] expectedOutput = new byte[] {
+ 0x09 /* Cable 0 Note-On */, (byte) 0x91, 0x22, 0x33};
+ usbMidiPacketConverter.encodeMidiPackets(input, input.length, 4);
+ byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
+ compareByteArrays(expectedOutput, output);
+ }
+
+ @Test
+ public void testEncoderLargePacketDoesNotCrash() {
+ for (long seed = 234; seed < 4000; seed += 666) {
+ Random rnd = new Random(seed);
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createEncoders(4);
+ for (int cableNumber = 0; cableNumber < 4; cableNumber++) {
+ byte[] input = generateRandomByteStream(rnd, 1003 /* arbitrary large size */);
+ usbMidiPacketConverter.encodeMidiPackets(input, input.length, cableNumber);
+ }
+ usbMidiPacketConverter.pullEncodedMidiPackets();
+ }
+ }
+
+ @Test
+ public void testEncodeDecode() {
+ final int bufferSize = 30;
+ final int numCables = 16;
+ final int bytesToEncodePerEncoding = 10;
+ byte[][] rawMidi = new byte[numCables][bufferSize];
+ for (long seed = 45; seed < 3000; seed += 300) {
+ Random rnd = new Random(seed);
+ for (int cableNumber = 0; cableNumber < numCables; cableNumber++) {
+ rawMidi[cableNumber] = generateRandomByteStream(rnd, bufferSize);
+
+ // Change the last byte to SysEx End.
+ // This way the encoder is guaranteed to flush all packets.
+ rawMidi[cableNumber][bufferSize - 1] = (byte) 0xF7;
+ }
+ UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
+ usbMidiPacketConverter.createEncoders(numCables);
+ // Encode packets and interweave them
+ for (int startByte = 0; startByte < bufferSize;
+ startByte += bytesToEncodePerEncoding) {
+ for (int cableNumber = 0; cableNumber < numCables; cableNumber++) {
+ byte[] bytesToEncode = Arrays.copyOfRange(rawMidi[cableNumber], startByte,
+ startByte + bytesToEncodePerEncoding);
+ usbMidiPacketConverter.encodeMidiPackets(bytesToEncode, bytesToEncode.length,
+ cableNumber);
+ }
+ }
+ byte[] usbMidi = usbMidiPacketConverter.pullEncodedMidiPackets();
+
+ usbMidiPacketConverter.createDecoders(numCables);
+
+ // Now decode the MIDI packets to check if they are the same as the original
+ usbMidiPacketConverter.decodeMidiPackets(usbMidi, usbMidi.length);
+ for (int cableNumber = 0; cableNumber < numCables; cableNumber++) {
+ byte[] decodedRawMidi = usbMidiPacketConverter.pullDecodedMidiPackets(cableNumber);
+ compareByteArrays(rawMidi[cableNumber], decodedRawMidi);
+ }
+ }
+ }
+}