Merge RP1A.200513.002
Change-Id: I4af65528de8e3ff3dce71e5de8249a9e658b0a1c
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 2aa2275..e888651 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -56,6 +56,7 @@
import android.os.BatteryStatsInternal;
import android.os.Binder;
import android.os.Handler;
+import android.os.LimitExceededException;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -1002,7 +1003,7 @@
}
if (isDebuggable) {
// Only throw the exception for debuggable apps.
- throw new IllegalStateException(
+ throw new LimitExceededException(
"schedule()/enqueue() called more than "
+ mQuotaTracker.getLimit(Category.SINGLE_CATEGORY)
+ " times in the past "
diff --git a/api/current.txt b/api/current.txt
index 467aa32..41a74cf 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -58326,9 +58326,9 @@
method public abstract void setAllowFileAccess(boolean);
method @Deprecated public abstract void setAllowFileAccessFromFileURLs(boolean);
method @Deprecated public abstract void setAllowUniversalAccessFromFileURLs(boolean);
- method public abstract void setAppCacheEnabled(boolean);
+ method @Deprecated public abstract void setAppCacheEnabled(boolean);
method @Deprecated public abstract void setAppCacheMaxSize(long);
- method public abstract void setAppCachePath(String);
+ method @Deprecated public abstract void setAppCachePath(String);
method public abstract void setBlockNetworkImage(boolean);
method public abstract void setBlockNetworkLoads(boolean);
method public abstract void setBuiltInZoomControls(boolean);
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index f314358..6428cf4 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -1317,7 +1317,7 @@
if (mSystemWd < 0) {
close(mInotifyFd);
mInotifyFd = -1;
- SLOGE("Could not add watch for %s", SYSTEM_DATA_DIR_PATH);
+ SLOGE("Could not add watch for %s: %s", SYSTEM_DATA_DIR_PATH, strerror(errno));
return NO_INIT;
}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 71dd4dd..1d9f20e 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -439,6 +439,7 @@
app_permission_groups_fragment_auto_revoke_action =
273 [(module) = "permissioncontroller"];
EvsUsageStatsReported evs_usage_stats_reported = 274 [(module) = "evs"];
+ AudioPowerUsageDataReported audio_power_usage_data_reported = 275;
SdkExtensionStatus sdk_extension_status = 354;
// StatsdStats tracks platform atoms with ids upto 500.
@@ -9689,3 +9690,59 @@
// The duration of the service
optional int64 duration_millis = 10;
}
+
+/**
+ * Logs audio power usage stats.
+ *
+ * Pushed from:
+ * frameworks/av/services/mediametrics/AudioPowerUsage.cpp
+ */
+message AudioPowerUsageDataReported {
+ /**
+ * Device used for input/output
+ *
+ * All audio devices please refer to below file:
+ * system/media/audio/include/system/audio-base.h
+ *
+ * Define our own enum values because we don't report all audio devices.
+ * Currently, we only report built-in audio devices such as handset, speaker,
+ * built-in mics, common audio devices such as wired headset, usb headset
+ * and bluetooth devices.
+ */
+ enum AudioDevice {
+ OUTPUT_EARPIECE = 0x1; // handset
+ OUTPUT_SPEAKER = 0x2; // dual speaker
+ OUTPUT_WIRED_HEADSET = 0x4; // 3.5mm headset
+ OUTPUT_USB_HEADSET = 0x8; // usb headset
+ OUTPUT_BLUETOOTH_SCO = 0x10; // bluetooth sco
+ OUTPUT_BLUETOOTH_A2DP = 0x20; // a2dp
+ OUTPUT_SPEAKER_SAFE = 0x40; // bottom speaker
+
+ INPUT_DEVICE_BIT = 0x40000000; // non-negative positive int32.
+ INPUT_BUILTIN_MIC = 0x40000001; // buildin mic
+ INPUT_BUILTIN_BACK_MIC = 0x40000002; // buildin back mic
+ INPUT_WIRED_HEADSET_MIC = 0x40000004; // 3.5mm headset mic
+ INPUT_USB_HEADSET_MIC = 0x40000008; // usb headset mic
+ INPUT_BLUETOOTH_SCO = 0x40000010; // bluetooth sco mic
+ }
+ optional AudioDevice audio_device = 1;
+
+ // Duration of the audio in seconds
+ optional int32 duration_secs = 2;
+
+ // Average volume (0 ... 1.0)
+ optional float average_volume = 3;
+
+ enum AudioType {
+ UNKNOWN_TYPE = 0;
+ VOICE_CALL_TYPE = 1; // voice call
+ VOIP_CALL_TYPE = 2; // voip call, including uplink and downlink
+ MEDIA_TYPE = 3; // music and system sound
+ RINGTONE_NOTIFICATION_TYPE = 4; // ringtone and notification
+ ALARM_TYPE = 5; // alarm type
+ // record type
+ CAMCORDER_TYPE = 6; // camcorder
+ RECORD_TYPE = 7; // other recording
+ }
+ optional AudioType type = 4;
+}
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 8ec0173..f56fa62 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -66,15 +66,6 @@
#define ATTRIBUTION_CHAIN_TYPE 0x09
#define ERROR_TYPE 0x0F
-LogEvent::LogEvent(const LogEvent& event) {
- mTagId = event.mTagId;
- mLogUid = event.mLogUid;
- mLogPid = event.mLogPid;
- mElapsedTimestampNs = event.mElapsedTimestampNs;
- mLogdTimestampNs = event.mLogdTimestampNs;
- mValues = event.mValues;
-}
-
LogEvent::LogEvent(int32_t uid, int32_t pid)
: mLogdTimestampNs(time(nullptr)), mLogUid(uid), mLogPid(pid) {
}
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 53fb5d9..a5f2460 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -216,7 +216,7 @@
/**
* Only use this if copy is absolutely needed.
*/
- LogEvent(const LogEvent&);
+ LogEvent(const LogEvent&) = default;
void parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
void parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index bfae632..af5fafb 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,6 +17,8 @@
package android.app;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.inMultiWindowMode;
import static android.os.Process.myUid;
import static java.lang.Character.MIN_VALUE;
@@ -947,9 +949,8 @@
/** @hide */
boolean mEnterAnimationComplete;
- /** Track last dispatched multi-window and PiP mode to client, internal debug purpose **/
- private Boolean mLastDispatchedIsInMultiWindowMode;
- private Boolean mLastDispatchedIsInPictureInPictureMode;
+ private boolean mIsInMultiWindowMode;
+ private boolean mIsInPictureInPictureMode;
private final WindowControllerCallback mWindowControllerCallback =
new WindowControllerCallback() {
@@ -2748,7 +2749,7 @@
* @return True if the activity is in multi-window mode.
*/
public boolean isInMultiWindowMode() {
- return mLastDispatchedIsInMultiWindowMode == Boolean.TRUE;
+ return mIsInMultiWindowMode;
}
/**
@@ -2791,7 +2792,7 @@
* @return True if the activity is in picture-in-picture mode.
*/
public boolean isInPictureInPictureMode() {
- return mLastDispatchedIsInPictureInPictureMode == Boolean.TRUE;
+ return mIsInPictureInPictureMode;
}
/**
@@ -7142,14 +7143,19 @@
writer.print(mResumed); writer.print(" mStopped=");
writer.print(mStopped); writer.print(" mFinished=");
writer.println(mFinished);
- writer.print(innerPrefix); writer.print("mLastDispatchedIsInMultiWindowMode=");
- writer.print(mLastDispatchedIsInMultiWindowMode);
- writer.print(" mLastDispatchedIsInPictureInPictureMode=");
- writer.println(mLastDispatchedIsInPictureInPictureMode);
+ writer.print(innerPrefix); writer.print("mIsInMultiWindowMode=");
+ writer.print(mIsInMultiWindowMode);
+ writer.print(" mIsInPictureInPictureMode=");
+ writer.println(mIsInPictureInPictureMode);
writer.print(innerPrefix); writer.print("mChangingConfigurations=");
writer.println(mChangingConfigurations);
writer.print(innerPrefix); writer.print("mCurrentConfig=");
writer.println(mCurrentConfig);
+ if (getResources().hasOverrideDisplayAdjustments()) {
+ writer.print(innerPrefix);
+ writer.print("FixedRotationAdjustments=");
+ writer.println(getResources().getDisplayAdjustments().getFixedRotationAdjustments());
+ }
mFragments.dumpLoaders(innerPrefix, fd, writer, args);
mFragments.getFragmentManager().dump(innerPrefix, fd, writer, args);
@@ -7977,6 +7983,11 @@
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
dispatchActivityPreCreated(icicle);
mCanEnterPictureInPicture = true;
+ // initialize mIsInMultiWindowMode and mIsInPictureInPictureMode before onCreate
+ final int windowingMode = getResources().getConfiguration().windowConfiguration
+ .getWindowingMode();
+ mIsInMultiWindowMode = inMultiWindowMode(windowingMode);
+ mIsInPictureInPictureMode = windowingMode == WINDOWING_MODE_PINNED;
restoreHasCurrentPermissionRequest(icicle);
if (persistentState != null) {
onCreate(icicle, persistentState);
@@ -8245,7 +8256,7 @@
if (mWindow != null) {
mWindow.onMultiWindowModeChanged();
}
- mLastDispatchedIsInMultiWindowMode = isInMultiWindowMode;
+ mIsInMultiWindowMode = isInMultiWindowMode;
onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
}
@@ -8258,7 +8269,7 @@
if (mWindow != null) {
mWindow.onPictureInPictureModeChanged(isInPictureInPictureMode);
}
- mLastDispatchedIsInPictureInPictureMode = isInPictureInPictureMode;
+ mIsInPictureInPictureMode = isInPictureInPictureMode;
onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 41e3891..a83ecf6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -157,6 +157,8 @@
import android.view.Choreographer;
import android.view.ContextThemeWrapper;
import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewDebug;
@@ -216,6 +218,7 @@
import java.util.TimeZone;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
final class RemoteServiceException extends AndroidRuntimeException {
public RemoteServiceException(String msg) {
@@ -406,6 +409,9 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final ResourcesManager mResourcesManager;
+ /** The active adjustments that override the {@link DisplayAdjustments} in resources. */
+ private ArrayList<Pair<IBinder, Consumer<DisplayAdjustments>>> mActiveRotationAdjustments;
+
// Registry of remote cancellation transports pending a reply with reply handles.
@GuardedBy("this")
private @Nullable Map<SafeCancellationTransport, CancellationSignal> mRemoteCancellations;
@@ -542,6 +548,12 @@
@UnsupportedAppUsage
boolean mPreserveWindow;
+ /**
+ * If non-null, the activity is launching with a specified rotation, the adjustments should
+ * be consumed before activity creation.
+ */
+ FixedRotationAdjustments mPendingFixedRotationAdjustments;
+
@LifecycleState
private int mLifecycleState = PRE_ON_CREATE;
@@ -558,7 +570,7 @@
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, boolean isForward,
ProfilerInfo profilerInfo, ClientTransactionHandler client,
- IBinder assistToken) {
+ IBinder assistToken, FixedRotationAdjustments fixedRotationAdjustments) {
this.token = token;
this.assistToken = assistToken;
this.ident = ident;
@@ -576,6 +588,7 @@
this.overrideConfig = overrideConfig;
this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo,
compatInfo);
+ mPendingFixedRotationAdjustments = fixedRotationAdjustments;
init();
}
@@ -3234,6 +3247,44 @@
sendMessage(H.CLEAN_UP_CONTEXT, cci);
}
+ @Override
+ public void handleFixedRotationAdjustments(@NonNull IBinder token,
+ @Nullable FixedRotationAdjustments fixedRotationAdjustments) {
+ final Consumer<DisplayAdjustments> override = fixedRotationAdjustments != null
+ ? displayAdjustments -> displayAdjustments.setFixedRotationAdjustments(
+ fixedRotationAdjustments)
+ : null;
+ if (!mResourcesManager.overrideTokenDisplayAdjustments(token, override)) {
+ // No resources are associated with the token.
+ return;
+ }
+ if (mActivities.get(token) == null) {
+ // Only apply the override to application for activity token because the appearance of
+ // activity is usually more sensitive to the application resources.
+ return;
+ }
+
+ // Apply the last override to application resources for compatibility. Because the Resources
+ // of Display can be from application, e.g.
+ // applicationContext.getSystemService(DisplayManager.class).getDisplay(displayId)
+ // and the deprecated usage:
+ // applicationContext.getSystemService(WindowManager.class).getDefaultDisplay();
+ final Consumer<DisplayAdjustments> appOverride;
+ if (mActiveRotationAdjustments == null) {
+ mActiveRotationAdjustments = new ArrayList<>(2);
+ }
+ if (override != null) {
+ mActiveRotationAdjustments.add(Pair.create(token, override));
+ appOverride = override;
+ } else {
+ mActiveRotationAdjustments.removeIf(adjustmentsPair -> adjustmentsPair.first == token);
+ appOverride = mActiveRotationAdjustments.isEmpty()
+ ? null
+ : mActiveRotationAdjustments.get(mActiveRotationAdjustments.size() - 1).second;
+ }
+ mInitialApplication.getResources().overrideDisplayAdjustments(appOverride);
+ }
+
/** Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
@@ -3447,6 +3498,13 @@
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
+ // The rotation adjustments must be applied before creating the activity, so the activity
+ // can get the adjusted display info during creation.
+ if (r.mPendingFixedRotationAdjustments != null) {
+ handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments);
+ r.mPendingFixedRotationAdjustments = null;
+ }
+
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
// For debugging purposes, if the activity's package name contains the value of
// the "debug.use-second-display" system property as a substring, then show
@@ -7496,7 +7554,15 @@
try {
super.rename(oldPath, newPath);
} catch (ErrnoException e) {
- if (e.errno == OsConstants.EXDEV && oldPath.startsWith("/storage/")) {
+ // On emulated volumes, we have bind mounts for /Android/data and
+ // /Android/obb, which prevents move from working across those directories
+ // and other directories on the filesystem. To work around that, try to
+ // recover by doing a copy instead.
+ // Note that we only do this for "/storage/emulated", because public volumes
+ // don't have these bind mounts, neither do private volumes that are not
+ // the primary storage.
+ if (e.errno == OsConstants.EXDEV && oldPath.startsWith("/storage/emulated")
+ && newPath.startsWith("/storage/emulated")) {
Log.v(TAG, "Recovering failed rename " + oldPath + " to " + newPath);
try {
Files.move(new File(oldPath).toPath(), new File(newPath).toPath(),
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d321288..b058dcd 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -3688,7 +3688,7 @@
/**
* @hide
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@code "
+ @UnsupportedAppUsage(/*maxTargetSdk = Build.VERSION_CODES.R,*/ publicAlternatives = "{@code "
+ "#getOpStr()}")
public int getOp() {
return mOp;
@@ -3707,7 +3707,7 @@
* @deprecated Use {@link #getLastAccessTime(int)} instead
*/
@Deprecated
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@code "
+ @UnsupportedAppUsage(/*maxTargetSdk = Build.VERSION_CODES.R,*/ publicAlternatives = "{@code "
+ "#getLastAccessTime(int)}")
public long getTime() {
return getLastAccessTime(OP_FLAGS_ALL);
@@ -3822,7 +3822,7 @@
* @deprecated Use {@link #getLastRejectTime(int)} instead
*/
@Deprecated
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@code "
+ @UnsupportedAppUsage(/*maxTargetSdk = Build.VERSION_CODES.R,*/ publicAlternatives = "{@code "
+ "#getLastRejectTime(int)}")
public long getRejectTime() {
return getLastRejectTime(OP_FLAGS_ALL);
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 83465b0..2df756e 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -25,6 +25,7 @@
import android.content.res.Configuration;
import android.os.IBinder;
import android.util.MergedConfiguration;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.ReferrerIntent;
@@ -167,6 +168,10 @@
/** Deliver app configuration change notification. */
public abstract void handleConfigurationChanged(Configuration config);
+ /** Apply addition adjustments to override display information. */
+ public abstract void handleFixedRotationAdjustments(IBinder token,
+ FixedRotationAdjustments fixedRotationAdjustments);
+
/**
* Get {@link android.app.ActivityThread.ActivityClientRecord} instance that corresponds to the
* provided token.
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 72f5813..1b6ca11 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -59,6 +59,7 @@
import java.util.List;
import java.util.Objects;
import java.util.WeakHashMap;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/** @hide */
@@ -1296,6 +1297,35 @@
}
}
+ /**
+ * Overrides the display adjustments of all resources which are associated with the given token.
+ *
+ * @param token The token that owns the resources.
+ * @param override The operation to override the existing display adjustments. If it is null,
+ * the override adjustments will be cleared.
+ * @return {@code true} if the override takes effect.
+ */
+ public boolean overrideTokenDisplayAdjustments(IBinder token,
+ @Nullable Consumer<DisplayAdjustments> override) {
+ boolean handled = false;
+ synchronized (this) {
+ final ActivityResources tokenResources = mActivityResourceReferences.get(token);
+ if (tokenResources == null) {
+ return false;
+ }
+ final ArrayList<WeakReference<Resources>> resourcesRefs =
+ tokenResources.activityResources;
+ for (int i = resourcesRefs.size() - 1; i >= 0; i--) {
+ final Resources res = resourcesRefs.get(i).get();
+ if (res != null) {
+ res.overrideDisplayAdjustments(override);
+ handled = true;
+ }
+ }
+ }
+ return handled;
+ }
+
private class UpdateHandler implements Resources.UpdateCallbacks {
/**
diff --git a/core/java/android/app/servertransaction/FixedRotationAdjustmentsItem.java b/core/java/android/app/servertransaction/FixedRotationAdjustmentsItem.java
new file mode 100644
index 0000000..6183d5f
--- /dev/null
+++ b/core/java/android/app/servertransaction/FixedRotationAdjustmentsItem.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
+
+import java.util.Objects;
+
+/**
+ * The request to update display adjustments for a rotated activity or window token.
+ * @hide
+ */
+public class FixedRotationAdjustmentsItem extends ClientTransactionItem {
+
+ /** The token who may have {@link android.content.res.Resources}. */
+ private IBinder mToken;
+
+ /**
+ * The adjustments for the display adjustments of resources. If it is null, the existing
+ * rotation adjustments will be dropped to restore natural state.
+ */
+ private FixedRotationAdjustments mFixedRotationAdjustments;
+
+ private FixedRotationAdjustmentsItem() {}
+
+ /** Obtain an instance initialized with provided params. */
+ public static FixedRotationAdjustmentsItem obtain(IBinder token,
+ FixedRotationAdjustments fixedRotationAdjustments) {
+ FixedRotationAdjustmentsItem instance =
+ ObjectPool.obtain(FixedRotationAdjustmentsItem.class);
+ if (instance == null) {
+ instance = new FixedRotationAdjustmentsItem();
+ }
+ instance.mToken = token;
+ instance.mFixedRotationAdjustments = fixedRotationAdjustments;
+
+ return instance;
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token,
+ PendingTransactionActions pendingActions) {
+ client.handleFixedRotationAdjustments(mToken, mFixedRotationAdjustments);
+ }
+
+ @Override
+ public void recycle() {
+ mToken = null;
+ mFixedRotationAdjustments = null;
+ ObjectPool.recycle(this);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mToken);
+ dest.writeTypedObject(mFixedRotationAdjustments, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final FixedRotationAdjustmentsItem other = (FixedRotationAdjustmentsItem) o;
+ return Objects.equals(mToken, other.mToken)
+ && Objects.equals(mFixedRotationAdjustments, other.mFixedRotationAdjustments);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Objects.hashCode(mToken);
+ result = 31 * result + Objects.hashCode(mFixedRotationAdjustments);
+ return result;
+ }
+
+ private FixedRotationAdjustmentsItem(Parcel in) {
+ mToken = in.readStrongBinder();
+ mFixedRotationAdjustments = in.readTypedObject(FixedRotationAdjustments.CREATOR);
+ }
+
+ public static final Creator<FixedRotationAdjustmentsItem> CREATOR =
+ new Creator<FixedRotationAdjustmentsItem>() {
+ public FixedRotationAdjustmentsItem createFromParcel(Parcel in) {
+ return new FixedRotationAdjustmentsItem(in);
+ }
+
+ public FixedRotationAdjustmentsItem[] newArray(int size) {
+ return new FixedRotationAdjustmentsItem[size];
+ }
+ };
+}
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 9ab6e7f..2e7b626 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -33,6 +33,7 @@
import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.Trace;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.ReferrerIntent;
@@ -64,6 +65,7 @@
private boolean mIsForward;
private ProfilerInfo mProfilerInfo;
private IBinder mAssistToken;
+ private FixedRotationAdjustments mFixedRotationAdjustments;
@Override
public void preExecute(ClientTransactionHandler client, IBinder token) {
@@ -79,7 +81,7 @@
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mIsForward,
- mProfilerInfo, client, mAssistToken);
+ mProfilerInfo, client, mAssistToken, mFixedRotationAdjustments);
client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -101,14 +103,14 @@
String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, boolean isForward, ProfilerInfo profilerInfo,
- IBinder assistToken) {
+ IBinder assistToken, FixedRotationAdjustments fixedRotationAdjustments) {
LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class);
if (instance == null) {
instance = new LaunchActivityItem();
}
setValues(instance, intent, ident, info, curConfig, overrideConfig, compatInfo, referrer,
voiceInteractor, procState, state, persistentState, pendingResults,
- pendingNewIntents, isForward, profilerInfo, assistToken);
+ pendingNewIntents, isForward, profilerInfo, assistToken, fixedRotationAdjustments);
return instance;
}
@@ -116,7 +118,7 @@
@Override
public void recycle() {
setValues(this, null, 0, null, null, null, null, null, null, 0, null, null, null, null,
- false, null, null);
+ false, null, null, null);
ObjectPool.recycle(this);
}
@@ -142,6 +144,7 @@
dest.writeBoolean(mIsForward);
dest.writeTypedObject(mProfilerInfo, flags);
dest.writeStrongBinder(mAssistToken);
+ dest.writeTypedObject(mFixedRotationAdjustments, flags);
}
/** Read from Parcel. */
@@ -156,7 +159,8 @@
in.createTypedArrayList(ResultInfo.CREATOR),
in.createTypedArrayList(ReferrerIntent.CREATOR), in.readBoolean(),
in.readTypedObject(ProfilerInfo.CREATOR),
- in.readStrongBinder());
+ in.readStrongBinder(),
+ in.readTypedObject(FixedRotationAdjustments.CREATOR));
}
public static final @android.annotation.NonNull Creator<LaunchActivityItem> CREATOR =
@@ -192,7 +196,8 @@
&& Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
&& mIsForward == other.mIsForward
&& Objects.equals(mProfilerInfo, other.mProfilerInfo)
- && Objects.equals(mAssistToken, other.mAssistToken);
+ && Objects.equals(mAssistToken, other.mAssistToken)
+ && Objects.equals(mFixedRotationAdjustments, other.mFixedRotationAdjustments);
}
@Override
@@ -212,6 +217,7 @@
result = 31 * result + (mIsForward ? 1 : 0);
result = 31 * result + Objects.hashCode(mProfilerInfo);
result = 31 * result + Objects.hashCode(mAssistToken);
+ result = 31 * result + Objects.hashCode(mFixedRotationAdjustments);
return result;
}
@@ -247,7 +253,7 @@
+ ",referrer=" + mReferrer + ",procState=" + mProcState + ",state=" + mState
+ ",persistentState=" + mPersistentState + ",pendingResults=" + mPendingResults
+ ",pendingNewIntents=" + mPendingNewIntents + ",profilerInfo=" + mProfilerInfo
- + " assistToken=" + mAssistToken
+ + ",assistToken=" + mAssistToken + ",rotationAdj=" + mFixedRotationAdjustments
+ "}";
}
@@ -257,7 +263,8 @@
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken) {
+ boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken,
+ FixedRotationAdjustments fixedRotationAdjustments) {
instance.mIntent = intent;
instance.mIdent = ident;
instance.mInfo = info;
@@ -274,5 +281,6 @@
instance.mIsForward = isForward;
instance.mProfilerInfo = profilerInfo;
instance.mAssistToken = assistToken;
+ instance.mFixedRotationAdjustments = fixedRotationAdjustments;
}
}
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 37baae3..0105896 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -51,6 +51,9 @@
void uninstall(in VersionedPackage versionedPackage, String callerPackageName, int flags,
in IntentSender statusReceiver, int userId);
+ void uninstallExistingPackage(in VersionedPackage versionedPackage, String callerPackageName,
+ in IntentSender statusReceiver, int userId);
+
void installExistingPackage(String packageName, int installFlags, int installReason,
in IntentSender statusReceiver, int userId, in List<String> whiteListedPermissions);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 8bebaff..f257326 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -235,6 +235,16 @@
void deletePackageVersioned(in VersionedPackage versionedPackage,
IPackageDeleteObserver2 observer, int userId, int flags);
+ /**
+ * Delete a package for a specific user.
+ *
+ * @param versionedPackage The package to delete.
+ * @param observer a callback to use to notify when the package deletion in finished.
+ * @param userId the id of the user for whom to delete the package
+ */
+ void deleteExistingPackageAsUser(in VersionedPackage versionedPackage,
+ IPackageDeleteObserver2 observer, int userId);
+
@UnsupportedAppUsage
String getInstallerPackageName(in String packageName);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 85a3986..ed75504 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -720,6 +720,27 @@
}
}
+ /**
+ * Uninstall the given package for the user for which this installer was created if the package
+ * will still exist for other users on the device.
+ *
+ * @param packageName The package to install.
+ * @param statusReceiver Where to deliver the result.
+ *
+ * {@hide}
+ */
+ @RequiresPermission(Manifest.permission.DELETE_PACKAGES)
+ public void uninstallExistingPackage(@NonNull String packageName,
+ @Nullable IntentSender statusReceiver) {
+ Objects.requireNonNull(packageName, "packageName cannot be null");
+ try {
+ mInstaller.uninstallExistingPackage(
+ new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+ mInstallerPackageName, statusReceiver, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/** {@hide} */
@SystemApi
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index c399bc7..0f1c876 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -81,6 +81,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.function.Consumer;
/**
* Class for accessing an application's resources. This sits on top of the
@@ -140,6 +141,9 @@
@UnsupportedAppUsage
private DrawableInflater mDrawableInflater;
+ /** Used to override the returned adjustments of {@link #getDisplayAdjustments}. */
+ private DisplayAdjustments mOverrideDisplayAdjustments;
+
/** Lock object used to protect access to {@link #mTmpValue}. */
private final Object mTmpValueLock = new Object();
@@ -2055,10 +2059,41 @@
/** @hide */
@UnsupportedAppUsage
public DisplayAdjustments getDisplayAdjustments() {
+ final DisplayAdjustments overrideDisplayAdjustments = mOverrideDisplayAdjustments;
+ if (overrideDisplayAdjustments != null) {
+ return overrideDisplayAdjustments;
+ }
return mResourcesImpl.getDisplayAdjustments();
}
/**
+ * Customize the display adjustments based on the current one in {@link #mResourcesImpl}, in
+ * order to isolate the effect with other instances of {@link Resource} that may share the same
+ * instance of {@link ResourcesImpl}.
+ *
+ * @param override The operation to override the existing display adjustments. If it is null,
+ * the override adjustments will be cleared.
+ * @hide
+ */
+ public void overrideDisplayAdjustments(@Nullable Consumer<DisplayAdjustments> override) {
+ if (override != null) {
+ mOverrideDisplayAdjustments = new DisplayAdjustments(
+ mResourcesImpl.getDisplayAdjustments());
+ override.accept(mOverrideDisplayAdjustments);
+ } else {
+ mOverrideDisplayAdjustments = null;
+ }
+ }
+
+ /**
+ * Return {@code true} if the override display adjustments have been set.
+ * @hide
+ */
+ public boolean hasOverrideDisplayAdjustments() {
+ return mOverrideDisplayAdjustments != null;
+ }
+
+ /**
* Return the current configuration that is in effect for this resource
* object. The returned object should be treated as read-only.
*
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 6905f83..d071037 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2182,11 +2182,13 @@
* <p>By using this control, the application gains a simpler way to control zoom, which can
* be a combination of optical and digital zoom. For example, a multi-camera system may
* contain more than one lens with different focal lengths, and the user can use optical
- * zoom by switching between lenses. Using zoomRatio has benefits in the scenarios below:
- * <em> Zooming in from a wide-angle lens to a telephoto lens: A floating-point ratio provides
- * better precision compared to an integer value of {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}.
- * </em> Zooming out from a wide lens to an ultrawide lens: zoomRatio supports zoom-out whereas
- * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} doesn't.</p>
+ * zoom by switching between lenses. Using zoomRatio has benefits in the scenarios below:</p>
+ * <ul>
+ * <li>Zooming in from a wide-angle lens to a telephoto lens: A floating-point ratio provides
+ * better precision compared to an integer value of {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}.</li>
+ * <li>Zooming out from a wide lens to an ultrawide lens: zoomRatio supports zoom-out whereas
+ * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} doesn't.</li>
+ * </ul>
* <p>To illustrate, here are several scenarios of different zoom ratios, crop regions,
* and output streams, for a hypothetical camera device with an active array of size
* <code>(2000,1500)</code>.</p>
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index be03502..ae04693 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2412,11 +2412,13 @@
* <p>By using this control, the application gains a simpler way to control zoom, which can
* be a combination of optical and digital zoom. For example, a multi-camera system may
* contain more than one lens with different focal lengths, and the user can use optical
- * zoom by switching between lenses. Using zoomRatio has benefits in the scenarios below:
- * <em> Zooming in from a wide-angle lens to a telephoto lens: A floating-point ratio provides
- * better precision compared to an integer value of {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}.
- * </em> Zooming out from a wide lens to an ultrawide lens: zoomRatio supports zoom-out whereas
- * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} doesn't.</p>
+ * zoom by switching between lenses. Using zoomRatio has benefits in the scenarios below:</p>
+ * <ul>
+ * <li>Zooming in from a wide-angle lens to a telephoto lens: A floating-point ratio provides
+ * better precision compared to an integer value of {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}.</li>
+ * <li>Zooming out from a wide lens to an ultrawide lens: zoomRatio supports zoom-out whereas
+ * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} doesn't.</li>
+ * </ul>
* <p>To illustrate, here are several scenarios of different zoom ratios, crop regions,
* and output streams, for a hypothetical camera device with an active array of size
* <code>(2000,1500)</code>.</p>
diff --git a/core/java/android/hardware/display/OWNERS b/core/java/android/hardware/display/OWNERS
new file mode 100644
index 0000000..9ca3910
--- /dev/null
+++ b/core/java/android/hardware/display/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+santoscordon@google.com
diff --git a/core/java/android/hardware/input/OWNERS b/core/java/android/hardware/input/OWNERS
new file mode 100644
index 0000000..0313a40
--- /dev/null
+++ b/core/java/android/hardware/input/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+svv@google.com
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index e371df0..0ec4fb8 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -1,3 +1,20 @@
+# Haptics
+per-file ExternalVibration.aidl = michaelwr@google.com
+per-file ExternalVibration.java = michaelwr@google.com
+per-file IExternalVibrationController.aidl = michaelwr@google.com
+per-file IExternalVibratorService.aidl = michaelwr@google.com
+per-file IVibratorService.aidl = michaelwr@google.com
+per-file NullVibrator.java = michaelwr@google.com
+per-file SystemVibrator.java = michaelwr@google.com
+per-file VibrationEffect.aidl = michaelwr@google.com
+per-file VibrationEffect.java = michaelwr@google.com
+per-file Vibrator.java = michaelwr@google.com
+
+# PowerManager
+per-file IPowerManager.aidl = michaelwr@google.com, santoscordon@google.com
+per-file PowerManager.java = michaelwr@google.com, santoscordon@google.com
+per-file PowerManagerInternal.java = michaelwr@google.com, santoscordon@google.com
+
# Zygote
per-file ZygoteProcess.java = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 236ea00..25bf430 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2582,8 +2582,8 @@
}
/**
- * Creates a user with the specified name and options. For non-admin users, default user
- * restrictions are going to be applied.
+ * Creates a user with the specified name and options.
+ * Default user restrictions will be applied.
* Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
*
* @param name the user's name
@@ -2602,8 +2602,8 @@
}
/**
- * Creates a user with the specified name and options. For non-admin users, default user
- * restrictions will be applied.
+ * Creates a user with the specified name and options.
+ * Default user restrictions will be applied.
*
* <p>Requires {@link android.Manifest.permission#MANAGE_USERS}.
* {@link android.Manifest.permission#CREATE_USERS} suffices if flags are in
@@ -2637,8 +2637,7 @@
}
/**
- * Pre-creates a user of the specified type. For non-admin users, default user
- * restrictions will be applied.
+ * Pre-creates a user of the specified type. Default user restrictions will be applied.
*
* <p>This method can be used by OEMs to "warm" up the user creation by pre-creating some users
* at the first boot, so they when the "real" user is created (for example,
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 06caa03..dea932d 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -56,15 +56,11 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.RemoteStream;
import com.android.internal.infra.ServiceConnector;
-import com.android.internal.os.TransferPipe;
import com.android.internal.util.CollectionUtils;
import libcore.util.EmptyArray;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -72,7 +68,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -88,6 +83,7 @@
public final class PermissionControllerManager {
private static final String TAG = PermissionControllerManager.class.getSimpleName();
+ private static final long REQUEST_TIMEOUT_MILLIS = 60000;
private static final long UNBIND_TIMEOUT_MILLIS = 10000;
private static final int CHUNK_SIZE = 4 * 1024;
@@ -227,6 +223,11 @@
}
@Override
+ protected long getRequestTimeoutMs() {
+ return REQUEST_TIMEOUT_MILLIS;
+ }
+
+ @Override
protected long getAutoDisconnectTimeoutMs() {
return UNBIND_TIMEOUT_MILLIS;
}
@@ -487,28 +488,12 @@
*
* @hide
*/
- public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args) {
- CompletableFuture<Throwable> dumpResult = new CompletableFuture<>();
- mRemoteService.postForResult(
- service -> TransferPipe.dumpAsync(service.asBinder(), args))
- .whenComplete(
- (dump, err) -> {
- try (FileOutputStream out = new FileOutputStream(fd)) {
- out.write(dump);
- } catch (IOException | NullPointerException e) {
- Log.e(TAG, "Could for forwards permission controller dump", e);
- }
-
- dumpResult.complete(err);
- });
-
+ public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) {
try {
- Throwable err = dumpResult.get(UNBIND_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- if (err != null) {
- throw err;
- }
- } catch (Throwable e) {
- Log.e(TAG, "Could not dump permission controller state", e);
+ mRemoteService.post(service -> service.asBinder().dump(fd, args))
+ .get(REQUEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ Log.e(TAG, "Could not get dump", e);
}
}
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index c6ede32..8ad35e7 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -499,6 +499,11 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ checkNotNull(fd, "fd");
+ checkNotNull(writer, "writer");
+
+ enforceSomePermissionsGrantedToCaller(Manifest.permission.GET_RUNTIME_PERMISSIONS);
+
PermissionControllerService.this.dump(fd, writer, args);
}
};
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index d94160c..62becc5 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -86,6 +86,16 @@
*/
public static final @RequestFlags int FLAG_PASSWORD_INPUT_TYPE = 0x4;
+ /**
+ * Indicates the view was not focused.
+ *
+ * <p><b>Note:</b> Defines the flag value to 0x10, because the flag value 0x08 has been defined
+ * in {@link AutofillManager}.</p>
+ *
+ * @hide
+ */
+ public static final @RequestFlags int FLAG_VIEW_NOT_FOCUSED = 0x10;
+
/** @hide */
public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;
@@ -165,7 +175,8 @@
@IntDef(flag = true, prefix = "FLAG_", value = {
FLAG_MANUAL_REQUEST,
FLAG_COMPATIBILITY_MODE_REQUEST,
- FLAG_PASSWORD_INPUT_TYPE
+ FLAG_PASSWORD_INPUT_TYPE,
+ FLAG_VIEW_NOT_FOCUSED
})
@Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
@@ -187,6 +198,8 @@
return "FLAG_COMPATIBILITY_MODE_REQUEST";
case FLAG_PASSWORD_INPUT_TYPE:
return "FLAG_PASSWORD_INPUT_TYPE";
+ case FLAG_VIEW_NOT_FOCUSED:
+ return "FLAG_VIEW_NOT_FOCUSED";
default: return Integer.toHexString(value);
}
}
@@ -248,7 +261,8 @@
mFlags,
FLAG_MANUAL_REQUEST
| FLAG_COMPATIBILITY_MODE_REQUEST
- | FLAG_PASSWORD_INPUT_TYPE);
+ | FLAG_PASSWORD_INPUT_TYPE
+ | FLAG_VIEW_NOT_FOCUSED);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
onConstructed();
@@ -384,7 +398,8 @@
mFlags,
FLAG_MANUAL_REQUEST
| FLAG_COMPATIBILITY_MODE_REQUEST
- | FLAG_PASSWORD_INPUT_TYPE);
+ | FLAG_PASSWORD_INPUT_TYPE
+ | FLAG_VIEW_NOT_FOCUSED);
this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
onConstructed();
@@ -405,10 +420,10 @@
};
@DataClass.Generated(
- time = 1588119440090L,
+ time = 1589280816805L,
codegenVersion = "1.0.15",
sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
- inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 4469fdb..8db1703 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -104,6 +104,14 @@
private int mCachedAppHeightCompat;
/**
+ * Indicates that the application is started in a different rotation than the real display, so
+ * the display information may be adjusted. That ensures the methods {@link #getRotation},
+ * {@link #getRealSize}, {@link #getRealMetrics}, and {@link #getCutout} are consistent with how
+ * the application window is laid out.
+ */
+ private boolean mMayAdjustByFixedRotation;
+
+ /**
* The default Display id, which is the id of the primary display assuming there is one.
*/
public static final int DEFAULT_DISPLAY = 0;
@@ -804,7 +812,9 @@
public int getRotation() {
synchronized (this) {
updateDisplayInfoLocked();
- return mDisplayInfo.rotation;
+ return mMayAdjustByFixedRotation
+ ? getDisplayAdjustments().getRotation(mDisplayInfo.rotation)
+ : mDisplayInfo.rotation;
}
}
@@ -828,7 +838,9 @@
public DisplayCutout getCutout() {
synchronized (this) {
updateDisplayInfoLocked();
- return mDisplayInfo.displayCutout;
+ return mMayAdjustByFixedRotation
+ ? getDisplayAdjustments().getDisplayCutout(mDisplayInfo.displayCutout)
+ : mDisplayInfo.displayCutout;
}
}
@@ -1140,6 +1152,9 @@
updateDisplayInfoLocked();
outSize.x = mDisplayInfo.logicalWidth;
outSize.y = mDisplayInfo.logicalHeight;
+ if (mMayAdjustByFixedRotation) {
+ getDisplayAdjustments().adjustSize(outSize, mDisplayInfo.rotation);
+ }
}
}
@@ -1159,6 +1174,9 @@
updateDisplayInfoLocked();
mDisplayInfo.getLogicalMetrics(outMetrics,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ if (mMayAdjustByFixedRotation) {
+ getDisplayAdjustments().adjustMetrics(outMetrics, mDisplayInfo.rotation);
+ }
}
}
@@ -1225,6 +1243,9 @@
}
}
}
+
+ mMayAdjustByFixedRotation = mResources != null
+ && mResources.hasOverrideDisplayAdjustments();
}
private void updateCachedAppSizeIfNeededLocked() {
@@ -1243,9 +1264,12 @@
public String toString() {
synchronized (this) {
updateDisplayInfoLocked();
- mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments());
+ final DisplayAdjustments adjustments = getDisplayAdjustments();
+ mDisplayInfo.getAppMetrics(mTempMetrics, adjustments);
return "Display id " + mDisplayId + ": " + mDisplayInfo
- + ", " + mTempMetrics + ", isValid=" + mIsValid;
+ + (mMayAdjustByFixedRotation
+ ? (", " + adjustments.getFixedRotationAdjustments() + ", ") : ", ")
+ + mTempMetrics + ", isValid=" + mIsValid;
}
}
diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java
index 27c2d5c..c726bee 100644
--- a/core/java/android/view/DisplayAdjustments.java
+++ b/core/java/android/view/DisplayAdjustments.java
@@ -21,6 +21,10 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.graphics.Point;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.DisplayMetrics;
import java.util.Objects;
@@ -30,6 +34,7 @@
private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
private final Configuration mConfiguration = new Configuration(Configuration.EMPTY);
+ private FixedRotationAdjustments mFixedRotationAdjustments;
@UnsupportedAppUsage
public DisplayAdjustments() {
@@ -44,6 +49,7 @@
public DisplayAdjustments(@NonNull DisplayAdjustments daj) {
setCompatibilityInfo(daj.mCompatInfo);
mConfiguration.setTo(daj.getConfiguration());
+ mFixedRotationAdjustments = daj.mFixedRotationAdjustments;
}
@UnsupportedAppUsage
@@ -84,11 +90,78 @@
return mConfiguration;
}
+ public void setFixedRotationAdjustments(FixedRotationAdjustments fixedRotationAdjustments) {
+ mFixedRotationAdjustments = fixedRotationAdjustments;
+ }
+
+ public FixedRotationAdjustments getFixedRotationAdjustments() {
+ return mFixedRotationAdjustments;
+ }
+
+ /** Returns {@code false} if the width and height of display should swap. */
+ private boolean noFlip(@Surface.Rotation int realRotation) {
+ final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+ if (rotationAdjustments == null) {
+ return true;
+ }
+ // Check if the delta is rotated by 90 degrees.
+ return (realRotation - rotationAdjustments.mRotation + 4) % 2 == 0;
+ }
+
+ /** Adjusts the given size if possible. */
+ public void adjustSize(@NonNull Point size, @Surface.Rotation int realRotation) {
+ if (noFlip(realRotation)) {
+ return;
+ }
+ final int w = size.x;
+ size.x = size.y;
+ size.y = w;
+ }
+
+ /** Adjusts the given metrics if possible. */
+ public void adjustMetrics(@NonNull DisplayMetrics metrics, @Surface.Rotation int realRotation) {
+ if (noFlip(realRotation)) {
+ return;
+ }
+ int w = metrics.widthPixels;
+ metrics.widthPixels = metrics.heightPixels;
+ metrics.heightPixels = w;
+
+ w = metrics.noncompatWidthPixels;
+ metrics.noncompatWidthPixels = metrics.noncompatHeightPixels;
+ metrics.noncompatHeightPixels = w;
+
+ float x = metrics.xdpi;
+ metrics.xdpi = metrics.ydpi;
+ metrics.ydpi = x;
+
+ x = metrics.noncompatXdpi;
+ metrics.noncompatXdpi = metrics.noncompatYdpi;
+ metrics.noncompatYdpi = x;
+ }
+
+ /** Returns the adjusted cutout if available. Otherwise the original cutout is returned. */
+ @Nullable
+ public DisplayCutout getDisplayCutout(@Nullable DisplayCutout realCutout) {
+ final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+ return rotationAdjustments != null && rotationAdjustments.mRotatedDisplayCutout != null
+ ? rotationAdjustments.mRotatedDisplayCutout
+ : realCutout;
+ }
+
+ /** Returns the adjusted rotation if available. Otherwise the original rotation is returned. */
+ @Surface.Rotation
+ public int getRotation(@Surface.Rotation int realRotation) {
+ final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+ return rotationAdjustments != null ? rotationAdjustments.mRotation : realRotation;
+ }
+
@Override
public int hashCode() {
int hash = 17;
hash = hash * 31 + Objects.hashCode(mCompatInfo);
hash = hash * 31 + Objects.hashCode(mConfiguration);
+ hash = hash * 31 + Objects.hashCode(mFixedRotationAdjustments);
return hash;
}
@@ -98,7 +171,82 @@
return false;
}
DisplayAdjustments daj = (DisplayAdjustments)o;
- return Objects.equals(daj.mCompatInfo, mCompatInfo) &&
- Objects.equals(daj.mConfiguration, mConfiguration);
+ return Objects.equals(daj.mCompatInfo, mCompatInfo)
+ && Objects.equals(daj.mConfiguration, mConfiguration)
+ && Objects.equals(daj.mFixedRotationAdjustments, mFixedRotationAdjustments);
+ }
+
+ /**
+ * An application can be launched in different rotation than the real display. This class
+ * provides the information to adjust the values returned by {@link #Display}.
+ * @hide
+ */
+ public static class FixedRotationAdjustments implements Parcelable {
+ /** The application-based rotation. */
+ @Surface.Rotation
+ final int mRotation;
+
+ /** Non-null if the device has cutout. */
+ @Nullable
+ final DisplayCutout mRotatedDisplayCutout;
+
+ public FixedRotationAdjustments(@Surface.Rotation int rotation, DisplayCutout cutout) {
+ mRotation = rotation;
+ mRotatedDisplayCutout = cutout;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 17;
+ hash = hash * 31 + mRotation;
+ hash = hash * 31 + Objects.hashCode(mRotatedDisplayCutout);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof FixedRotationAdjustments)) {
+ return false;
+ }
+ final FixedRotationAdjustments other = (FixedRotationAdjustments) o;
+ return mRotation == other.mRotation
+ && Objects.equals(mRotatedDisplayCutout, other.mRotatedDisplayCutout);
+ }
+
+ @Override
+ public String toString() {
+ return "FixedRotationAdjustments{rotation=" + Surface.rotationToString(mRotation)
+ + " cutout=" + mRotatedDisplayCutout + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mRotation);
+ dest.writeTypedObject(
+ new DisplayCutout.ParcelableWrapper(mRotatedDisplayCutout), flags);
+ }
+
+ private FixedRotationAdjustments(Parcel in) {
+ mRotation = in.readInt();
+ final DisplayCutout.ParcelableWrapper cutoutWrapper =
+ in.readTypedObject(DisplayCutout.ParcelableWrapper.CREATOR);
+ mRotatedDisplayCutout = cutoutWrapper != null ? cutoutWrapper.get() : null;
+ }
+
+ public static final Creator<FixedRotationAdjustments> CREATOR =
+ new Creator<FixedRotationAdjustments>() {
+ public FixedRotationAdjustments createFromParcel(Parcel in) {
+ return new FixedRotationAdjustments(in);
+ }
+
+ public FixedRotationAdjustments[] newArray(int size) {
+ return new FixedRotationAdjustments[size];
+ }
+ };
}
}
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
new file mode 100644
index 0000000..7b60f2e
--- /dev/null
+++ b/core/java/android/view/OWNERS
@@ -0,0 +1,15 @@
+# Display
+per-file Display.java = michaelwr@google.com, santoscordon@google.com
+per-file DisplayInfo.java = michaelwr@google.com, santoscordon@google.com
+
+# Haptics
+per-file HapticFeedbackConstants.java = michaelwr@google.com, santoscordon@google.com
+
+# Input
+per-file IInputMonitorHost.aidl = michaelwr@google.com, svv@google.com
+per-file Input*.java = michaelwr@google.com, svv@google.com
+per-file Input*.aidl = michaelwr@google.com, svv@google.com
+per-file KeyEvent.java = michaelwr@google.com, svv@google.com
+per-file MotionEvent.java = michaelwr@google.com, svv@google.com
+per-file PointerIcon.java = michaelwr@google.com, svv@google.com
+per-file SimulatedDpad.java = michaelwr@google.com, svv@google.com
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index a660218..1d56154 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -220,6 +220,8 @@
private static native long nativeAcquireFrameRateFlexibilityToken();
private static native void nativeReleaseFrameRateFlexibilityToken(long token);
+ private static native void nativeSetFixedTransformHint(long transactionObj, long nativeObject,
+ int transformHint);
private final CloseGuard mCloseGuard = CloseGuard.get();
private String mName;
@@ -2310,6 +2312,39 @@
}
/**
+ * Provide the graphic producer a transform hint if the layer and its children are
+ * in an orientation different from the display's orientation. The caller is responsible
+ * for clearing this transform hint if the layer is no longer in a fixed orientation.
+ *
+ * The transform hint is used to prevent allocating a buffer of different size when a
+ * layer is rotated. The producer can choose to consume the hint and allocate the buffer
+ * with the same size.
+ *
+ * @return This Transaction.
+ * @hide
+ */
+ @NonNull
+ public Transaction setFixedTransformHint(@NonNull SurfaceControl sc,
+ @Surface.Rotation int transformHint) {
+ checkPreconditions(sc);
+ nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, transformHint);
+ return this;
+ }
+
+ /**
+ * Clearing any transform hint if set on this layer.
+ *
+ * @return This Transaction.
+ * @hide
+ */
+ @NonNull
+ public Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) {
+ checkPreconditions(sc);
+ nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, -1/* INVALID_ROTATION */);
+ return this;
+ }
+
+ /**
* Set the Z-order for a given SurfaceControl, relative to it's siblings.
* If two siblings share the same Z order the ordering is undefined. Surfaces
* with a negative Z will be placed below the parent surface.
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 76fe6b5f..553e3c8 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -18,6 +18,7 @@
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
+import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
import static android.view.autofill.Helper.sDebug;
import static android.view.autofill.Helper.sVerbose;
import static android.view.autofill.Helper.toList;
@@ -879,7 +880,11 @@
* @param view view requesting the new autofill context.
*/
public void requestAutofill(@NonNull View view) {
- notifyViewEntered(view, FLAG_MANUAL_REQUEST);
+ int flags = FLAG_MANUAL_REQUEST;
+ if (!view.isFocused()) {
+ flags |= FLAG_VIEW_NOT_FOCUSED;
+ }
+ notifyViewEntered(view, flags);
}
/**
@@ -926,7 +931,11 @@
* @param absBounds absolute boundaries of the virtual view in the screen.
*/
public void requestAutofill(@NonNull View view, int virtualId, @NonNull Rect absBounds) {
- notifyViewEntered(view, virtualId, absBounds, FLAG_MANUAL_REQUEST);
+ int flags = FLAG_MANUAL_REQUEST;
+ if (!view.isFocused()) {
+ flags |= FLAG_VIEW_NOT_FOCUSED;
+ }
+ notifyViewEntered(view, virtualId, absBounds, flags);
}
/**
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 35dd576..e224e84 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1118,6 +1118,9 @@
* {@link #setAppCachePath}.
*
* @param flag {@code true} if the WebView should enable Application Caches
+ * @deprecated The Application Cache API is deprecated and this method will
+ * become a no-op on all Android versions once support is
+ * removed in Chromium. Consider using Service Workers instead.
*/
public abstract void setAppCacheEnabled(boolean flag);
@@ -1130,6 +1133,9 @@
* @param appCachePath a String path to the directory containing
* Application Caches files.
* @see #setAppCacheEnabled
+ * @deprecated The Application Cache API is deprecated and this method will
+ * become a no-op on all Android versions once support is
+ * removed in Chromium. Consider using Service Workers instead.
*/
public abstract void setAppCachePath(String appCachePath);
@@ -1142,7 +1148,7 @@
* It is recommended to leave the maximum size set to the default value.
*
* @param appCacheMaxSize the maximum size in bytes
- * @deprecated In future quota will be managed automatically.
+ * @deprecated Quota is managed automatically; this method is a no-op.
*/
@Deprecated
public abstract void setAppCacheMaxSize(long appCacheMaxSize);
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 891d535..a3c29a8 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -19,6 +19,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
import static com.android.internal.util.ArrayUtils.convertToLongArray;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -52,6 +53,7 @@
import android.widget.Toast;
import com.android.internal.R;
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.util.function.pooled.PooledLambda;
import java.lang.annotation.Retention;
@@ -267,16 +269,21 @@
}
private AlertDialog createShortcutWarningDialog(int userId) {
- final String warningMessage = mContext.getString(
- R.string.accessibility_shortcut_toogle_warning);
+ List<AccessibilityTarget> targets = getTargets(mContext, ACCESSIBILITY_SHORTCUT_KEY);
+ if (targets.size() == 0) {
+ return null;
+ }
+
+ // Avoid non-a11y users accidentally turning shortcut on without reading this carefully.
+ // Put "don't turn on" as the primary action.
final AlertDialog alertDialog = mFrameworkObjectProvider.getAlertDialogBuilder(
// Use SystemUI context so we pick up any theme set in a vendor overlay
mFrameworkObjectProvider.getSystemUiContext())
- .setTitle(R.string.accessibility_shortcut_warning_dialog_title)
- .setMessage(warningMessage)
+ .setTitle(getShortcutWarningTitle(targets))
+ .setMessage(getShortcutWarningMessage(targets))
.setCancelable(false)
- .setPositiveButton(R.string.leave_accessibility_shortcut_on, null)
- .setNegativeButton(R.string.disable_accessibility_shortcut,
+ .setNegativeButton(R.string.accessibility_shortcut_on, null)
+ .setPositiveButton(R.string.accessibility_shortcut_off,
(DialogInterface d, int which) -> {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
@@ -297,6 +304,32 @@
return alertDialog;
}
+ private String getShortcutWarningTitle(List<AccessibilityTarget> targets) {
+ if (targets.size() == 1) {
+ return mContext.getString(
+ R.string.accessibility_shortcut_single_service_warning_title,
+ targets.get(0).getLabel());
+ }
+ return mContext.getString(
+ R.string.accessibility_shortcut_multiple_service_warning_title);
+ }
+
+ private String getShortcutWarningMessage(List<AccessibilityTarget> targets) {
+ if (targets.size() == 1) {
+ return mContext.getString(
+ R.string.accessibility_shortcut_single_service_warning,
+ targets.get(0).getLabel());
+ }
+
+ final StringBuilder sb = new StringBuilder();
+ for (AccessibilityTarget target : targets) {
+ sb.append(mContext.getString(R.string.accessibility_shortcut_multiple_service_list,
+ target.getLabel()));
+ }
+ return mContext.getString(R.string.accessibility_shortcut_multiple_service_warning,
+ sb.toString());
+ }
+
private AccessibilityServiceInfo getInfoForTargetService() {
final ComponentName targetComponentName = getShortcutTargetComponentName();
if (targetComponentName == null) {
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index 37871d0..d756593 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -38,7 +38,7 @@
* Abstract base class for creating various target related to accessibility service,
* accessibility activity, and white listing feature.
*/
-abstract class AccessibilityTarget implements TargetOperations, OnTargetSelectedListener,
+public abstract class AccessibilityTarget implements TargetOperations, OnTargetSelectedListener,
OnTargetCheckedChangeListener {
private Context mContext;
@ShortcutType
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index f63cbe0..60a102a 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -53,19 +53,61 @@
/**
* Collection of utilities for accessibility target.
*/
-final class AccessibilityTargetHelper {
+public final class AccessibilityTargetHelper {
private AccessibilityTargetHelper() {}
- static List<AccessibilityTarget> getTargets(Context context,
+ /**
+ * Returns list of {@link AccessibilityTarget} of assigned accessibility shortcuts from
+ * {@link AccessibilityManager#getAccessibilityShortcutTargets} including accessibility
+ * feature's package name, component id, etc.
+ *
+ * @param context The context of the application.
+ * @param shortcutType The shortcut type.
+ * @return The list of {@link AccessibilityTarget}.
+ * @hide
+ */
+ public static List<AccessibilityTarget> getTargets(Context context,
@ShortcutType int shortcutType) {
- final List<AccessibilityTarget> targets = getInstalledTargets(context, shortcutType);
- final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
- final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
- targets.removeIf(target -> !requiredTargets.contains(target.getId()));
+ // List all accessibility target
+ final List<AccessibilityTarget> installedTargets = getInstalledTargets(context,
+ shortcutType);
- return targets;
+ // List accessibility shortcut target
+ final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ final List<String> assignedTargets = am.getAccessibilityShortcutTargets(shortcutType);
+
+ // Get the list of accessibility shortcut target in all accessibility target
+ final List<AccessibilityTarget> results = new ArrayList<>();
+ for (String assignedTarget : assignedTargets) {
+ for (AccessibilityTarget installedTarget : installedTargets) {
+ if (!MAGNIFICATION_CONTROLLER_NAME.contentEquals(assignedTarget)) {
+ final ComponentName assignedTargetComponentName =
+ ComponentName.unflattenFromString(assignedTarget);
+ final ComponentName targetComponentName = ComponentName.unflattenFromString(
+ installedTarget.getId());
+ if (assignedTargetComponentName.equals(targetComponentName)) {
+ results.add(installedTarget);
+ continue;
+ }
+ }
+ if (assignedTarget.contentEquals(installedTarget.getId())) {
+ results.add(installedTarget);
+ }
+ }
+ }
+ return results;
}
+ /**
+ * Returns list of {@link AccessibilityTarget} of the installed accessibility service,
+ * accessibility activity, and white listing feature including accessibility feature's package
+ * name, component id, etc.
+ *
+ * @param context The context of the application.
+ * @param shortcutType The shortcut type.
+ * @return The list of {@link AccessibilityTarget}.
+ */
static List<AccessibilityTarget> getInstalledTargets(Context context,
@ShortcutType int shortcutType) {
final List<AccessibilityTarget> targets = new ArrayList<>();
@@ -110,9 +152,10 @@
private static List<AccessibilityTarget> getAccessibilityServiceTargets(Context context,
@ShortcutType int shortcutType) {
- final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
+ final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
final List<AccessibilityServiceInfo> installedServices =
- ams.getInstalledAccessibilityServiceList();
+ am.getInstalledAccessibilityServiceList();
if (installedServices == null) {
return Collections.emptyList();
}
@@ -136,9 +179,10 @@
private static List<AccessibilityTarget> getAccessibilityActivityTargets(Context context,
@ShortcutType int shortcutType) {
- final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class);
+ final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
final List<AccessibilityShortcutInfo> installedServices =
- ams.getInstalledAccessibilityShortcutListAsUser(context,
+ am.getInstalledAccessibilityShortcutListAsUser(context,
ActivityManager.getCurrentUser());
if (installedServices == null) {
return Collections.emptyList();
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
index e50b010..9ee0b0e 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
@@ -142,7 +142,8 @@
*/
public static boolean isAccessibilityServiceEnabled(Context context,
@NonNull String componentId) {
- final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+ final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
final List<AccessibilityServiceInfo> enabledServices =
am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 100422f..31ccb6c3 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -137,7 +137,8 @@
*/
public static boolean isShortcutContained(Context context, @ShortcutType int shortcutType,
@NonNull String componentId) {
- final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+ final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
final List<String> requiredTargets = am.getAccessibilityShortcutTargets(shortcutType);
return requiredTargets.contains(componentId);
}
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 2f048c9..a50a522 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -68,6 +68,7 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Predicate;
import java.util.regex.Pattern;
/**
@@ -381,17 +382,51 @@
return result;
}
+ /**
+ * This method is similar to
+ * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns
+ * all children documents including hidden directories/files.
+ *
+ * <p>
+ * In a scoped storage world, access to "Android/data" style directories are hidden for privacy
+ * reasons. This method may show privacy sensitive data, so its usage should only be in
+ * restricted modes.
+ *
+ * @param parentDocumentId the directory to return children for.
+ * @param projection list of {@link Document} columns to put into the
+ * cursor. If {@code null} all supported columns should be
+ * included.
+ * @param sortOrder how to order the rows, formatted as an SQL
+ * {@code ORDER BY} clause (excluding the ORDER BY itself).
+ * Passing {@code null} will use the default sort order, which
+ * may be unordered. This ordering is a hint that can be used to
+ * prioritize how data is fetched from the network, but UI may
+ * always enforce a specific ordering
+ * @throws FileNotFoundException when parent document doesn't exist or query fails
+ */
+ protected Cursor queryChildDocumentsShowAll(
+ String parentDocumentId, String[] projection, String sortOrder)
+ throws FileNotFoundException {
+ return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true);
+ }
+
@Override
public Cursor queryChildDocuments(
String parentDocumentId, String[] projection, String sortOrder)
throws FileNotFoundException {
+ // Access to some directories is hidden for privacy reasons.
+ return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow);
+ }
+ private Cursor queryChildDocuments(
+ String parentDocumentId, String[] projection, String sortOrder,
+ @NonNull Predicate<File> filter) throws FileNotFoundException {
final File parent = getFileForDocId(parentDocumentId);
final MatrixCursor result = new DirectoryCursor(
resolveProjection(projection), parentDocumentId, parent);
if (parent.isDirectory()) {
for (File file : FileUtils.listFilesOrEmpty(parent)) {
- if (!shouldHide(file)) {
+ if (filter.test(file)) {
includeFile(result, null, file);
}
}
@@ -617,6 +652,10 @@
return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches());
}
+ private boolean shouldShow(@NonNull File file) {
+ return !shouldHide(file);
+ }
+
protected boolean shouldBlockFromTree(@NonNull String docId) {
return false;
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 0a7a6e5..d816285 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1380,7 +1380,7 @@
public void registerStrongAuthTracker(final StrongAuthTracker strongAuthTracker) {
try {
- getLockSettings().registerStrongAuthTracker(strongAuthTracker.mStub);
+ getLockSettings().registerStrongAuthTracker(strongAuthTracker.getStub());
} catch (RemoteException e) {
throw new RuntimeException("Could not register StrongAuthTracker");
}
@@ -1388,7 +1388,7 @@
public void unregisterStrongAuthTracker(final StrongAuthTracker strongAuthTracker) {
try {
- getLockSettings().unregisterStrongAuthTracker(strongAuthTracker.mStub);
+ getLockSettings().unregisterStrongAuthTracker(strongAuthTracker.getStub());
} catch (RemoteException e) {
Log.e(TAG, "Could not unregister StrongAuthTracker", e);
}
@@ -1751,7 +1751,7 @@
}
}
- protected final IStrongAuthTracker.Stub mStub = new IStrongAuthTracker.Stub() {
+ private final IStrongAuthTracker.Stub mStub = new IStrongAuthTracker.Stub() {
@Override
public void onStrongAuthRequiredChanged(@StrongAuthFlags int strongAuthFlags,
int userId) {
@@ -1766,6 +1766,10 @@
}
};
+ public IStrongAuthTracker.Stub getStub() {
+ return mStub;
+ }
+
private class H extends Handler {
static final int MSG_ON_STRONG_AUTH_REQUIRED_CHANGED = 1;
static final int MSG_ON_IS_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED = 2;
diff --git a/core/java/com/android/internal/widget/OWNERS b/core/java/com/android/internal/widget/OWNERS
new file mode 100644
index 0000000..cca39ea
--- /dev/null
+++ b/core/java/com/android/internal/widget/OWNERS
@@ -0,0 +1 @@
+per-file PointerLocationView.java = michaelwr@google.com, svv@google.com
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 7ff15f2..d7d8621 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -5,5 +5,16 @@
# Connectivity
per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
+# Display
+per-file android_hardware_display_* = michaelwr@google.com, santoscordon@google.com
+
+# Input
+per-file android_hardware_input* = michaelwr@google.com, svv@google.com
+per-file android_view_Input* = michaelwr@google.com, svv@google.com
+per-file android_view_KeyCharacterMap.* = michaelwr@google.com, svv@google.com
+per-file android_view_*KeyEvent.* = michaelwr@google.com, svv@google.com
+per-file android_view_*MotionEvent.* = michaelwr@google.com, svv@google.com
+per-file android_view_PointerIcon.* = michaelwr@google.com, svv@google.com
+
# Zygote
per-file com_android_internal_os_Zygote.*,fd_utils.* = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 36b4b6a..e553a78 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -645,6 +645,14 @@
token->decStrong((void*)nativeAcquireFrameRateFlexibilityToken);
}
+static void nativeSetFixedTransformHint(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObject, jint transformHint) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ transaction->setFixedTransformHint(ctrl, transformHint);
+}
+
static jlongArray nativeGetPhysicalDisplayIds(JNIEnv* env, jclass clazz) {
const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds();
jlongArray array = env->NewLongArray(displayIds.size());
@@ -1644,6 +1652,7 @@
(void*)nativeSetGlobalShadowSettings },
{"nativeGetHandle", "(J)J",
(void*)nativeGetHandle },
+ {"nativeSetFixedTransformHint", "(JJI)V", (void*)nativeSetFixedTransformHint},
};
int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/res/res/values-mcc334-mnc020/config.xml b/core/res/res/values-mcc334-mnc020/config.xml
index 4aad534..82b3ee6 100644
--- a/core/res/res/values-mcc334-mnc020/config.xml
+++ b/core/res/res/values-mcc334-mnc020/config.xml
@@ -19,6 +19,6 @@
<resources>
<bool name="config_use_sim_language_file">false</bool>
- <bool name="config_pdp_rejeect_enable_retry">true</bool>
+ <bool name="config_pdp_reject_enable_retry">true</bool>
<integer name="config_pdp_reject_retry_delay_ms">45000</integer>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d1a1534..39215e6 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4392,12 +4392,6 @@
<!-- Used in multiple service warning to list current features. [CHAR LIMIT=none] -->
<string name="accessibility_shortcut_multiple_service_list">\t• <xliff:g id="service" example="TalkBack">%1$s</xliff:g>\n</string>
- <!-- Dialog title for dialog shown when the TalkBack shortcut is activated, and we want to confirm that the user understands what's going to happen. [CHAR LIMIT=none] -->
- <string name="accessibility_shortcut_talkback_warning_title">Turn on TalkBack?</string>
-
- <!-- Message shown in dialog when user is in the process of enabling the TalkBack via the volume buttons shortcut for the first time. [CHAR LIMIT=none] -->
- <string name="accessibility_shortcut_talkback_warning">Holding down both volume keys for a few seconds turns on TalkBack, a screen reader that is helpful for people who are blind or have low vision. TalkBack completely changes how your device works.\n\nYou can change this shortcut to another feature in Settings > Accessibility.</string>
-
<!-- Dialog title for dialog shown when this accessibility shortcut is activated, and we want to confirm that the user understands what's going to happen. [CHAR LIMIT=none] -->
<string name="accessibility_shortcut_single_service_warning_title">Turn on <xliff:g id="service" example="TalkBack">%1$s</xliff:g>?</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8d8ab58..23e553d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3239,12 +3239,15 @@
<java-symbol type="integer" name="config_debugSystemServerPssThresholdBytes" />
<!-- Accessibility Shortcut -->
- <java-symbol type="string" name="accessibility_shortcut_warning_dialog_title" />
- <java-symbol type="string" name="accessibility_shortcut_toogle_warning" />
+ <java-symbol type="string" name="accessibility_shortcut_single_service_warning_title" />
+ <java-symbol type="string" name="accessibility_shortcut_single_service_warning" />
+ <java-symbol type="string" name="accessibility_shortcut_multiple_service_warning_title" />
+ <java-symbol type="string" name="accessibility_shortcut_multiple_service_warning" />
+ <java-symbol type="string" name="accessibility_shortcut_multiple_service_list" />
+ <java-symbol type="string" name="accessibility_shortcut_on" />
+ <java-symbol type="string" name="accessibility_shortcut_off" />
<java-symbol type="string" name="accessibility_shortcut_enabling_service" />
<java-symbol type="string" name="accessibility_shortcut_disabling_service" />
- <java-symbol type="string" name="disable_accessibility_shortcut" />
- <java-symbol type="string" name="leave_accessibility_shortcut_on" />
<java-symbol type="string" name="color_inversion_feature_name" />
<java-symbol type="string" name="color_correction_feature_name" />
<java-symbol type="string" name="config_defaultAccessibilityService" />
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 107fe3f..6c23125 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -144,11 +144,12 @@
IBinder assistToken = new Binder();
LaunchActivityItem emptyItem = LaunchActivityItem.obtain(null, 0, null, null, null, null,
- null, null, 0, null, null, null, null, false, null, null);
+ null, null, 0, null, null, null, null, false, null, null, null);
LaunchActivityItem item = LaunchActivityItem.obtain(intent, ident, activityInfo,
config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(),
- true /* isForward */, null /* profilerInfo */, assistToken);
+ true /* isForward */, null /* profilerInfo */, assistToken,
+ null /* fixedRotationAdjustments */);
assertNotSame(item, emptyItem);
assertFalse(item.equals(emptyItem));
@@ -158,7 +159,8 @@
LaunchActivityItem item2 = LaunchActivityItem.obtain(intent, ident, activityInfo,
config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(),
- true /* isForward */, null /* profilerInfo */, assistToken);
+ true /* isForward */, null /* profilerInfo */, assistToken,
+ null /* fixedRotationAdjustments */);
assertSame(item, item2);
assertFalse(item2.equals(emptyItem));
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 09ea1b1..3c32c71 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -267,7 +267,7 @@
null /* voiceInteractor */, 0 /* procState */, null /* state */,
null /* persistentState */, null /* pendingResults */,
null /* pendingNewIntents */, false /* isForward */, null /* profilerInfo */,
- null /* assistToken*/));
+ null /* assistToken */, null /* fixedRotationAdjustments */));
launchTransaction.addCallback(launchItem);
mExecutor.execute(launchTransaction);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 47f9323..3f8d9ef 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -52,6 +52,9 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
+import android.view.DisplayCutout;
+import android.view.Surface;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -187,11 +190,14 @@
bundle.putParcelable("data", new ParcelableData(1));
PersistableBundle persistableBundle = new PersistableBundle();
persistableBundle.putInt("k", 4);
+ FixedRotationAdjustments fixedRotationAdjustments = new FixedRotationAdjustments(
+ Surface.ROTATION_90, DisplayCutout.NO_CUTOUT);
LaunchActivityItem item = LaunchActivityItem.obtain(intent, ident, activityInfo,
config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(),
- true /* isForward */, null /* profilerInfo */, new Binder());
+ true /* isForward */, null /* profilerInfo */, new Binder(),
+ fixedRotationAdjustments);
writeAndPrepareForReading(item);
// Read from parcel and assert
@@ -340,6 +346,22 @@
assertTrue(transaction.equals(result));
}
+ @Test
+ public void testFixedRotationAdjustments() {
+ ClientTransaction transaction = ClientTransaction.obtain(new StubAppThread(),
+ null /* activityToken */);
+ transaction.addCallback(FixedRotationAdjustmentsItem.obtain(new Binder(),
+ new FixedRotationAdjustments(Surface.ROTATION_270, DisplayCutout.NO_CUTOUT)));
+
+ writeAndPrepareForReading(transaction);
+
+ // Read from parcel and assert
+ ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(transaction.hashCode(), result.hashCode());
+ assertTrue(transaction.equals(result));
+ }
+
/** Write to {@link #mParcel} and reset its position to prepare for reading from the start. */
private void writeAndPrepareForReading(Parcelable parcelable) {
parcelable.writeToParcel(mParcel, 0 /* flags */);
diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
new file mode 100644
index 0000000..4d04a7a
--- /dev/null
+++ b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.usage;
+
+import static junit.framework.Assert.fail;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.ArrayUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+/**
+ * These tests verify that all fields defined in {@link UsageStats} and {@link UsageEvents.Event}
+ * are all known fields. This ensures that newly added fields or refactorings are accounted for in
+ * the usagestatsservice.proto and usagestatsservice_v2.proto files.
+ *
+ * Note: verification for {@link com.android.server.usage.IntervalStats} fields is located in
+ * {@link com.android.server.usage.IntervalStatsTests}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UsageStatsPersistenceTest {
+
+ // All fields in this list are defined in UsageStats and persisted - please ensure they're
+ // defined correctly in both usagestatsservice.proto and usagestatsservice_v2.proto
+ private static final String[] USAGESTATS_PERSISTED_FIELDS = {"mBeginTimeStamp", "mEndTimeStamp",
+ "mPackageName", "mPackageToken", "mLastEvent", "mAppLaunchCount", "mChooserCounts",
+ "mLastTimeUsed", "mTotalTimeInForeground", "mLastTimeForegroundServiceUsed",
+ "mTotalTimeForegroundServiceUsed", "mLastTimeVisible", "mTotalTimeVisible"};
+ // All fields in this list are defined in UsageStats but not persisted
+ private static final String[] USAGESTATS_IGNORED_FIELDS = {"CREATOR", "mActivities",
+ "mForegroundServices", "mLaunchCount", "mChooserCountsObfuscated"};
+
+ @Test
+ public void testUsageStatsFields() {
+ final UsageStats stats = new UsageStats();
+ final Field[] fields = stats.getClass().getDeclaredFields();
+ for (Field field : fields) {
+ if (!(ArrayUtils.contains(USAGESTATS_PERSISTED_FIELDS, field.getName())
+ || ArrayUtils.contains(USAGESTATS_IGNORED_FIELDS, field.getName()))) {
+ fail("Found an unknown field: " + field.getName() + ". Please correctly update "
+ + "either USAGESTATS_PERSISTED_FIELDS or USAGESTATS_IGNORED_FIELDS.");
+ }
+ }
+ }
+
+ // All fields in this list are defined in UsageEvents.Event and persisted - please ensure
+ // they're defined correctly in both usagestatsservice.proto and usagestatsservice_v2.proto
+ private static final String[] USAGEEVENTS_PERSISTED_FIELDS = {"mPackage", "mPackageToken",
+ "mClass", "mClassToken", "mTimeStamp", "mFlags", "mEventType", "mConfiguration",
+ "mShortcutId", "mShortcutIdToken", "mBucketAndReason", "mInstanceId",
+ "mNotificationChannelId", "mNotificationChannelIdToken", "mTaskRootPackage",
+ "mTaskRootPackageToken", "mTaskRootClass", "mTaskRootClassToken", "mLocusId",
+ "mLocusIdToken"};
+ // All fields in this list are defined in UsageEvents.Event but not persisted
+ private static final String[] USAGEEVENTS_IGNORED_FIELDS = {"mAction", "mContentAnnotations",
+ "mContentType", "DEVICE_EVENT_PACKAGE_NAME", "FLAG_IS_PACKAGE_INSTANT_APP",
+ "VALID_FLAG_BITS", "UNASSIGNED_TOKEN", "MAX_EVENT_TYPE"};
+ // All fields in this list are final constants defining event types and not persisted
+ private static final String[] EVENT_TYPES = {"NONE", "ACTIVITY_DESTROYED", "ACTIVITY_PAUSED",
+ "ACTIVITY_RESUMED", "ACTIVITY_STOPPED", "CHOOSER_ACTION", "CONFIGURATION_CHANGE",
+ "CONTINUE_PREVIOUS_DAY", "CONTINUING_FOREGROUND_SERVICE", "DEVICE_SHUTDOWN",
+ "DEVICE_STARTUP", "END_OF_DAY", "FLUSH_TO_DISK", "FOREGROUND_SERVICE_START",
+ "FOREGROUND_SERVICE_STOP", "KEYGUARD_HIDDEN", "KEYGUARD_SHOWN", "LOCUS_ID_SET",
+ "MOVE_TO_BACKGROUND", "MOVE_TO_FOREGROUND", "NOTIFICATION_INTERRUPTION",
+ "NOTIFICATION_SEEN", "ROLLOVER_FOREGROUND_SERVICE", "SCREEN_INTERACTIVE",
+ "SCREEN_NON_INTERACTIVE", "SHORTCUT_INVOCATION", "SLICE_PINNED", "SLICE_PINNED_PRIV",
+ "STANDBY_BUCKET_CHANGED", "SYSTEM_INTERACTION", "USER_INTERACTION", "USER_STOPPED",
+ "USER_UNLOCKED"};
+
+ @Test
+ public void testUsageEventsFields() {
+ final UsageEvents.Event event = new UsageEvents.Event();
+ final Field[] fields = event.getClass().getDeclaredFields();
+ for (Field field : fields) {
+ final String name = field.getName();
+ if (!(ArrayUtils.contains(USAGEEVENTS_PERSISTED_FIELDS, name)
+ || ArrayUtils.contains(USAGEEVENTS_IGNORED_FIELDS, name)
+ || ArrayUtils.contains(EVENT_TYPES, name))) {
+ fail("Found an unknown field: " + name + ". Please correctly update either "
+ + "USAGEEVENTS_PERSISTED_FIELDS or USAGEEVENTS_IGNORED_FIELDS. If this "
+ + "field is a new event type, please update EVENT_TYPES instead.");
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 4114b28..efcd458 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -259,4 +259,35 @@
expectedConfig2.orientation = Configuration.ORIENTATION_LANDSCAPE;
assertEquals(expectedConfig2, resources2.getConfiguration());
}
+
+ @SmallTest
+ public void testOverrideDisplayAdjustments() {
+ final int originalOverrideDensity = 200;
+ final int overrideDisplayDensity = 400;
+ final Binder token = new Binder();
+ final Configuration overrideConfig = new Configuration();
+ overrideConfig.densityDpi = originalOverrideDensity;
+ final Resources resources = mResourcesManager.createBaseTokenResources(
+ token, APP_ONE_RES_DIR, null /* splitResDirs */, null /* overlayDirs */,
+ null /* libDirs */, Display.DEFAULT_DISPLAY, overrideConfig,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null /* classLoader */,
+ null /* loaders */);
+
+ // Update the override.
+ boolean handled = mResourcesManager.overrideTokenDisplayAdjustments(token,
+ adjustments -> adjustments.getConfiguration().densityDpi = overrideDisplayDensity);
+
+ assertTrue(handled);
+ assertTrue(resources.hasOverrideDisplayAdjustments());
+ assertEquals(overrideDisplayDensity,
+ resources.getDisplayAdjustments().getConfiguration().densityDpi);
+
+ // Clear the override.
+ handled = mResourcesManager.overrideTokenDisplayAdjustments(token, null /* override */);
+
+ assertTrue(handled);
+ assertFalse(resources.hasOverrideDisplayAdjustments());
+ assertEquals(originalOverrideDensity,
+ resources.getDisplayAdjustments().getConfiguration().densityDpi);
+ }
}
diff --git a/core/tests/coretests/src/android/hardware/display/OWNERS b/core/tests/coretests/src/android/hardware/display/OWNERS
new file mode 100644
index 0000000..9ca3910
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/display/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+santoscordon@google.com
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
new file mode 100644
index 0000000..1a28b73
--- /dev/null
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -0,0 +1,9 @@
+# Display
+per-file BrightnessLimit.java = michaelwr@google.com, santoscordon@google.com
+
+# Haptics
+per-file ExternalVibrationTest.java = michaelwr@google.com
+per-file VibrationEffectTest.java = michaelwr@google.com
+
+# Power
+per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com
diff --git a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
index afbf8db..2fc42e9 100644
--- a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
+++ b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
@@ -19,6 +19,9 @@
import static org.junit.Assert.assertEquals;
import android.content.res.Configuration;
+import android.graphics.Point;
+import android.util.DisplayMetrics;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -67,4 +70,38 @@
assertEquals(configuration, newAdjustments.getConfiguration());
}
+
+ @Test
+ public void testFixedRotationAdjustments() {
+ final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
+ final int realRotation = Surface.ROTATION_0;
+ final int fixedRotation = Surface.ROTATION_90;
+
+ mDisplayAdjustments.setFixedRotationAdjustments(
+ new FixedRotationAdjustments(fixedRotation, null /* cutout */));
+
+ final int w = 1000;
+ final int h = 2000;
+ final Point size = new Point(w, h);
+ mDisplayAdjustments.adjustSize(size, realRotation);
+
+ assertEquals(fixedRotation, mDisplayAdjustments.getRotation(realRotation));
+ assertEquals(new Point(h, w), size);
+
+ final DisplayMetrics metrics = new DisplayMetrics();
+ metrics.xdpi = metrics.noncompatXdpi = w;
+ metrics.widthPixels = metrics.noncompatWidthPixels = w;
+ metrics.ydpi = metrics.noncompatYdpi = h;
+ metrics.heightPixels = metrics.noncompatHeightPixels = h;
+
+ final DisplayMetrics flippedMetrics = new DisplayMetrics();
+ flippedMetrics.xdpi = flippedMetrics.noncompatXdpi = h;
+ flippedMetrics.widthPixels = flippedMetrics.noncompatWidthPixels = h;
+ flippedMetrics.ydpi = flippedMetrics.noncompatYdpi = w;
+ flippedMetrics.heightPixels = flippedMetrics.noncompatHeightPixels = w;
+
+ mDisplayAdjustments.adjustMetrics(metrics, realRotation);
+
+ assertEquals(flippedMetrics, metrics);
+ }
}
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
new file mode 100644
index 0000000..a3a3e7c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -0,0 +1,4 @@
+# Input
+per-file *MotionEventTest.* = michaelwr@google.com, svv@google.com
+per-file *KeyEventTest.* = michaelwr@google.com, svv@google.com
+per-file VelocityTest.java = michaelwr@google.com, svv@google.com
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index b21504c..c17c36e 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -93,6 +93,7 @@
@RunWith(AndroidJUnit4.class)
public class AccessibilityShortcutControllerTest {
private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name";
+ private static final CharSequence PACKAGE_NAME_STRING = "Service name";
private static final String SERVICE_NAME_SUMMARY = "Summary";
private static final long VIBRATOR_PATTERN_1 = 100L;
private static final long VIBRATOR_PATTERN_2 = 150L;
@@ -150,6 +151,8 @@
new AccessibilityManager(mHandler, mAccessibilityManagerService, 0);
when(mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext))
.thenReturn(accessibilityManager);
+ when(mContext.getSystemService(Context.ACCESSIBILITY_SERVICE))
+ .thenReturn(accessibilityManager);
when(mFrameworkObjectProvider.getAlertDialogBuilder(mContext))
.thenReturn(mAlertDialogBuilder);
when(mFrameworkObjectProvider.makeToastFromText(eq(mContext), anyObject(), anyInt()))
@@ -166,13 +169,13 @@
ResolveInfo resolveInfo = mock(ResolveInfo.class);
resolveInfo.serviceInfo = mock(ServiceInfo.class);
resolveInfo.serviceInfo.applicationInfo = mApplicationInfo;
- when(resolveInfo.loadLabel(anyObject())).thenReturn("Service name");
+ when(resolveInfo.loadLabel(anyObject())).thenReturn(PACKAGE_NAME_STRING);
when(mServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
when(mServiceInfo.getComponentName())
.thenReturn(ComponentName.unflattenFromString(SERVICE_NAME_STRING));
when(mServiceInfo.loadSummary(any())).thenReturn(SERVICE_NAME_SUMMARY);
- when(mAlertDialogBuilder.setTitle(anyInt())).thenReturn(mAlertDialogBuilder);
+ when(mAlertDialogBuilder.setTitle(anyObject())).thenReturn(mAlertDialogBuilder);
when(mAlertDialogBuilder.setCancelable(anyBoolean())).thenReturn(mAlertDialogBuilder);
when(mAlertDialogBuilder.setMessage(anyObject())).thenReturn(mAlertDialogBuilder);
when(mAlertDialogBuilder.setPositiveButton(anyInt(), anyObject()))
@@ -324,7 +327,8 @@
assertEquals(1, Settings.Secure.getInt(
mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0));
- verify(mResources).getString(R.string.accessibility_shortcut_toogle_warning);
+ verify(mResources).getString(
+ R.string.accessibility_shortcut_single_service_warning_title, PACKAGE_NAME_STRING);
verify(mAlertDialog).show();
verify(mAccessibilityManagerService, atLeastOnce()).getInstalledAccessibilityServiceList(
anyInt());
@@ -376,16 +380,20 @@
ArgumentCaptor<DialogInterface.OnClickListener> captor =
ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
- verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.disable_accessibility_shortcut),
+ verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
captor.capture());
- // Call the button callback
- captor.getValue().onClick(null, 0);
+ // Call the button callback, if one exists
+ if (captor.getValue() != null) {
+ captor.getValue().onClick(null, 0);
+ }
assertTrue(TextUtils.isEmpty(
Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)));
+ assertEquals(0, Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN));
}
@Test
- public void testClickingLeaveOnButtonInDialog_shouldLeaveShortcutReady() throws Exception {
+ public void testClickingTurnOnButtonInDialog_shouldLeaveShortcutReady() throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
configureValidShortcutService();
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
@@ -393,8 +401,8 @@
ArgumentCaptor<DialogInterface.OnClickListener> captor =
ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
- verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.leave_accessibility_shortcut_on),
- captor.capture());
+ verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.accessibility_shortcut_on),
+ captor.capture());
// Call the button callback, if one exists
if (captor.getValue() != null) {
captor.getValue().onClick(null, 0);
@@ -402,7 +410,7 @@
assertEquals(SERVICE_NAME_STRING,
Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
assertEquals(1, Settings.Secure.getInt(
- mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN));
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN));
}
@Test
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 0390ac6..1cdc75a 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -225,7 +225,8 @@
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null /* referrer */,
null /* voiceInteractor */, null /* state */, null /* persistentState */,
null /* pendingResults */, null /* pendingNewIntents */, true /* isForward */,
- null /* profilerInfo */, mThread /* client */, null /* asssitToken */);
+ null /* profilerInfo */, mThread /* client */, null /* asssitToken */,
+ null /* fixedRotationAdjustments */);
}
@Override
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index dfb7a16..1b1a624 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -87,6 +87,13 @@
}
prebuilt_etc {
+ name: "privapp_whitelist_com.android.car.secondaryhome",
+ sub_dir: "permissions",
+ src: "com.android.car.secondaryhome.xml",
+ filename_from_src: true,
+}
+
+prebuilt_etc {
name: "privapp_whitelist_com.android.car.settings",
sub_dir: "permissions",
src: "com.android.car.settings.xml",
diff --git a/data/etc/car/com.android.car.secondaryhome.xml b/data/etc/car/com.android.car.secondaryhome.xml
index c74b86e..a8af906 100644
--- a/data/etc/car/com.android.car.secondaryhome.xml
+++ b/data/etc/car/com.android.car.secondaryhome.xml
@@ -20,5 +20,7 @@
<permission name="android.permission.ACTIVITY_EMBEDDING"/>
<!-- Required to send notification to current user-->
<permission name="android.permission.MANAGE_USERS"/>
+ <!-- Required for CarNotificationLib -->
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
</privapp-permissions>
</permissions>
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 97b448a..c8f065a 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -70,9 +70,9 @@
* {@link Bitmap} objects.
*
* <p>To use it, first create a {@link Source Source} using one of the
- * {@code createSource} overloads. For example, to decode from a {@link File}, call
- * {@link #createSource(File)} and pass the result to {@link #decodeDrawable(Source)}
- * or {@link #decodeBitmap(Source)}:
+ * {@code createSource} overloads. For example, to decode from a {@link Uri}, call
+ * {@link #createSource(ContentResolver, Uri)} and pass the result to
+ * {@link #decodeDrawable(Source)} or {@link #decodeBitmap(Source)}:
*
* <pre class="prettyprint">
* File file = new File(...);
@@ -1032,7 +1032,11 @@
/**
* Create a new {@link Source Source} from a {@link java.io.File}.
- *
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with files hosted outside your app, use an API like
+ * {@link #createSource(Callable)} or
+ * {@link #createSource(ContentResolver, Uri)}.
* @return a new Source object, which can be passed to
* {@link #decodeDrawable decodeDrawable} or
* {@link #decodeBitmap decodeBitmap}.
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 7d14ef5..6179b48 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -98,7 +98,7 @@
final Handler mHandler;
@GuardedBy("sRouterLock")
- private boolean mShouldUpdateRoutes;
+ private boolean mShouldUpdateRoutes = true;
private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
private volatile OnGetControllerHintsListener mOnGetControllerHintsListener;
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 0dc019c..4ebfce8 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -100,6 +100,7 @@
.getSystemService(Context.MEDIA_SESSION_SERVICE);
mPackageName = mContext.getPackageName();
mHandler = new Handler(context.getMainLooper());
+ mHandler.post(this::getOrCreateClient);
}
/**
@@ -118,18 +119,6 @@
Log.w(TAG, "Ignoring to add the same callback twice.");
return;
}
-
- synchronized (sLock) {
- if (mClient == null) {
- Client client = new Client();
- try {
- mMediaRouterService.registerManager(client, mPackageName);
- mClient = client;
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to register media router manager.", ex);
- }
- }
- }
}
/**
@@ -144,21 +133,6 @@
Log.w(TAG, "unregisterCallback: Ignore unknown callback. " + callback);
return;
}
-
- synchronized (sLock) {
- if (mCallbackRecords.size() == 0) {
- if (mClient != null) {
- try {
- mMediaRouterService.unregisterManager(mClient);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to unregister media router manager", ex);
- }
- mClient = null;
- }
- mRoutes.clear();
- mPreferredFeaturesMap.clear();
- }
- }
}
/**
@@ -314,10 +288,7 @@
*/
@NonNull
public List<RoutingSessionInfo> getActiveSessions() {
- Client client;
- synchronized (sLock) {
- client = mClient;
- }
+ Client client = getOrCreateClient();
if (client != null) {
try {
return mMediaRouterService.getActiveSessions(client);
@@ -380,10 +351,7 @@
return;
}
- Client client;
- synchronized (sLock) {
- client = mClient;
- }
+ Client client = getOrCreateClient();
if (client != null) {
try {
int requestId = mNextRequestId.getAndIncrement();
@@ -419,10 +387,7 @@
return;
}
- Client client;
- synchronized (sLock) {
- client = mClient;
- }
+ Client client = getOrCreateClient();
if (client != null) {
try {
int requestId = mNextRequestId.getAndIncrement();
@@ -451,10 +416,7 @@
return;
}
- Client client;
- synchronized (sLock) {
- client = mClient;
- }
+ Client client = getOrCreateClient();
if (client != null) {
try {
int requestId = mNextRequestId.getAndIncrement();
@@ -710,15 +672,12 @@
return;
}
- Client client;
- synchronized (sLock) {
- client = mClient;
- }
+ Client client = getOrCreateClient();
if (client != null) {
try {
int requestId = mNextRequestId.getAndIncrement();
mMediaRouterService.selectRouteWithManager(
- mClient, requestId, sessionInfo.getId(), route);
+ client, requestId, sessionInfo.getId(), route);
} catch (RemoteException ex) {
Log.e(TAG, "selectRoute: Failed to send a request.", ex);
}
@@ -755,15 +714,12 @@
return;
}
- Client client;
- synchronized (sLock) {
- client = mClient;
- }
+ Client client = getOrCreateClient();
if (client != null) {
try {
int requestId = mNextRequestId.getAndIncrement();
mMediaRouterService.deselectRouteWithManager(
- mClient, requestId, sessionInfo.getId(), route);
+ client, requestId, sessionInfo.getId(), route);
} catch (RemoteException ex) {
Log.e(TAG, "deselectRoute: Failed to send a request.", ex);
}
@@ -794,14 +750,11 @@
int requestId = mNextRequestId.getAndIncrement();
mTransferRequests.add(new TransferRequest(requestId, sessionInfo, route));
- Client client;
- synchronized (sLock) {
- client = mClient;
- }
+ Client client = getOrCreateClient();
if (client != null) {
try {
mMediaRouterService.transferToRouteWithManager(
- mClient, requestId, sessionInfo.getId(), route);
+ client, requestId, sessionInfo.getId(), route);
} catch (RemoteException ex) {
Log.e(TAG, "transferToRoute: Failed to send a request.", ex);
}
@@ -821,15 +774,12 @@
public void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
- Client client;
- synchronized (sLock) {
- client = mClient;
- }
+ Client client = getOrCreateClient();
if (client != null) {
try {
int requestId = mNextRequestId.getAndIncrement();
mMediaRouterService.releaseSessionWithManager(
- mClient, requestId, sessionInfo.getId());
+ client, requestId, sessionInfo.getId());
} catch (RemoteException ex) {
Log.e(TAG, "releaseSession: Failed to send a request", ex);
}
@@ -857,6 +807,23 @@
sessionInfo.getOwnerPackageName());
}
+ private Client getOrCreateClient() {
+ synchronized (sLock) {
+ if (mClient != null) {
+ return mClient;
+ }
+ Client client = new Client();
+ try {
+ mMediaRouterService.registerManager(client, mPackageName);
+ mClient = client;
+ return client;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to register media router manager.", ex);
+ }
+ }
+ return null;
+ }
+
/**
* Interface for receiving events about media routing changes.
*/
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index b5e2213..c4d27ec 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -23,8 +23,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.media.projection.IMediaProjection;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -86,6 +84,12 @@
* capture request. Will be null if the result from the
* startActivityForResult() is anything other than RESULT_OK.
*
+ * Starting from Android {@link android.os.Build.VERSION_CODES#R}, if your application requests
+ * the {@link android.Manifest.permission#SYSTEM_ALERT_WINDOW} permission, and the
+ * user has not explicitly denied it, the permission will be automatically granted until the
+ * projection is stopped. This allows for user controls to be displayed on top of the screen
+ * being captured.
+ *
* @param resultCode The result code from {@link android.app.Activity#onActivityResult(int,
* int, android.content.Intent)}
* @param resultData The resulting data from {@link android.app.Activity#onActivityResult(int,
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index eee797a..c05c21c 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -603,6 +603,11 @@
assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
+ @Test
+ public void testGetActiveSessions_returnsNonEmptyList() {
+ assertFalse(mManager.getActiveSessions().isEmpty());
+ }
+
Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures)
throws Exception {
CountDownLatch addedLatch = new CountDownLatch(1);
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 0c70e10..8f919c3 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -275,6 +275,13 @@
return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
}
+ @Override
+ public Cursor queryChildDocumentsForManage(
+ String parentDocId, String[] projection, String sortOrder)
+ throws FileNotFoundException {
+ return queryChildDocumentsShowAll(parentDocId, projection, sortOrder);
+ }
+
/**
* Check that the directory is the root of storage or blocked file from tree.
*
diff --git a/packages/InputDevices/OWNERS b/packages/InputDevices/OWNERS
new file mode 100644
index 0000000..0313a40
--- /dev/null
+++ b/packages/InputDevices/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+svv@google.com
diff --git a/packages/SystemUI/res/color/kg_user_switcher_rounded_background_color.xml b/packages/SystemUI/res/color/kg_user_switcher_rounded_background_color.xml
index b16d038..3660dc4 100644
--- a/packages/SystemUI/res/color/kg_user_switcher_rounded_background_color.xml
+++ b/packages/SystemUI/res/color/kg_user_switcher_rounded_background_color.xml
@@ -17,6 +17,7 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_activated="true" android:color="@color/kg_user_switcher_activated_background_color" />
+ <item android:state_activated="true" android_state_enabled="true" android:color="@color/kg_user_switcher_activated_background_color" />
+ <item android:state_pressed="true" android:state_enabled="true" android:color="@color/kg_user_switcher_activated_background_color" />
<item android:color="@android:color/transparent" />
-</selector>
\ No newline at end of file
+</selector>
diff --git a/packages/SystemUI/res/drawable/qs_media_background.xml b/packages/SystemUI/res/drawable/qs_media_background.xml
index e79c9a4..80db3be 100644
--- a/packages/SystemUI/res/drawable/qs_media_background.xml
+++ b/packages/SystemUI/res/drawable/qs_media_background.xml
@@ -19,4 +19,4 @@
systemui:rippleMinSize="30dp"
systemui:rippleMaxSize="135dp"
systemui:highlight="15"
- systemui:cornerRadius="@dimen/qs_media_corner_radius" />
\ No newline at end of file
+ systemui:cornerRadius="?android:attr/dialogCornerRadius" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
index d89f329..da76c8d 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
@@ -40,7 +40,7 @@
android:layout_height="wrap_content"
style="@style/TextAppearance.AuthCredential.Description"/>
- <EditText
+ <ImeAwareEditText
android:id="@+id/lockPassword"
android:layout_width="208dp"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/keyguard_media_header.xml b/packages/SystemUI/res/layout/keyguard_media_header.xml
index 20ec10c..a520719 100644
--- a/packages/SystemUI/res/layout/keyguard_media_header.xml
+++ b/packages/SystemUI/res/layout/keyguard_media_header.xml
@@ -45,109 +45,4 @@
android:layout_height="match_parent"
/>
- <!-- Layout for media controls. -->
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/keyguard_media_view"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center"
- android:padding="16dp"
- >
- <ImageView
- android:id="@+id/album_art"
- android:layout_width="@dimen/qs_media_album_size"
- android:layout_height="@dimen/qs_media_album_size"
- android:layout_marginRight="16dp"
- android:layout_weight="0"
- />
-
- <!-- Media information -->
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- >
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- >
- <com.android.internal.widget.CachingIconView
- android:id="@+id/icon"
- android:layout_width="16dp"
- android:layout_height="16dp"
- android:layout_marginEnd="5dp"
- />
- <TextView
- android:id="@+id/app_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="14sp"
- android:singleLine="true"
- />
- </LinearLayout>
-
- <!-- Song name -->
- <TextView
- android:id="@+id/header_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textSize="18sp"
- android:paddingBottom="6dp"
- android:gravity="center"/>
-
- <!-- Artist name -->
- <TextView
- android:id="@+id/header_artist"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:fontFamily="@*android:string/config_bodyFontFamily"
- android:textSize="14sp"
- android:singleLine="true"
- />
- </LinearLayout>
-
- <!-- Controls -->
- <LinearLayout
- android:id="@+id/media_actions"
- android:orientation="horizontal"
- android:layoutDirection="ltr"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center"
- android:layout_gravity="center"
- >
- <ImageButton
- style="@style/MediaPlayer.Button"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:gravity="center"
- android:visibility="gone"
- android:id="@+id/action0"
- />
- <ImageButton
- style="@style/MediaPlayer.Button"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:gravity="center"
- android:visibility="gone"
- android:id="@+id/action1"
- />
- <ImageButton
- style="@style/MediaPlayer.Button"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:gravity="center"
- android:visibility="gone"
- android:id="@+id/action2"
- />
- </LinearLayout>
- </LinearLayout>
-
</com.android.systemui.statusbar.notification.stack.MediaHeaderView>
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
index 149446c..03e7467 100644
--- a/packages/SystemUI/res/layout/media_carousel.xml
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -16,20 +16,22 @@
-->
<!-- Carousel for media controls -->
-<HorizontalScrollView
+<com.android.systemui.media.UnboundHorizontalScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:padding="@dimen/qs_media_padding"
android:scrollbars="none"
- android:visibility="gone"
+ android:clipChildren="false"
+ android:clipToPadding="false"
>
<LinearLayout
android:id="@+id/media_carousel"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:clipChildren="false"
+ android:clipToPadding="false"
>
<!-- QSMediaPlayers will be added here dynamically -->
</LinearLayout>
-</HorizontalScrollView>
+</com.android.systemui.media.UnboundHorizontalScrollView>
diff --git a/packages/SystemUI/res/layout/qqs_media_panel.xml b/packages/SystemUI/res/layout/qqs_media_panel.xml
deleted file mode 100644
index 2e86732..0000000
--- a/packages/SystemUI/res/layout/qqs_media_panel.xml
+++ /dev/null
@@ -1,90 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<!-- Layout for QQS media controls -->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/qqs_media_controls"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:gravity="center"
- android:paddingTop="16dp"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
- android:paddingBottom="12dp"
- android:background="@drawable/qs_media_background"
- >
- <!-- Top line: icon + song name -->
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:gravity="center"
- android:layout_marginBottom="12dp"
- >
- <com.android.internal.widget.CachingIconView
- android:id="@+id/icon"
- android:layout_width="14dp"
- android:layout_height="14dp"
- android:layout_marginEnd="5dp"
- />
- <TextView
- android:id="@+id/header_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:singleLine="true"
- />
- </LinearLayout>
-
- <!-- Bottom section: controls -->
- <LinearLayout
- android:id="@+id/media_actions"
- android:orientation="horizontal"
- android:layoutDirection="ltr"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- >
- <ImageButton
- style="@style/MediaPlayer.Button"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:gravity="center"
- android:visibility="gone"
- android:id="@+id/action0"
- />
- <ImageButton
- style="@style/MediaPlayer.Button"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:gravity="center"
- android:visibility="gone"
- android:id="@+id/action1"
- />
- <ImageButton
- style="@style/MediaPlayer.Button"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:gravity="center"
- android:visibility="gone"
- android:id="@+id/action2"
- />
- </LinearLayout>
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 0c9ce39..ebfd0a0 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -23,7 +23,6 @@
android:layout_height="@dimen/qs_footer_height"
android:layout_marginStart="@dimen/qs_footer_margin"
android:layout_marginEnd="@dimen/qs_footer_margin"
- android:elevation="4dp"
android:background="@android:color/transparent"
android:baselineAligned="false"
android:clickable="false"
@@ -128,13 +127,4 @@
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
</com.android.keyguard.AlphaOptimizedLinearLayout>
</LinearLayout>
- <View
- android:id="@+id/qs_drag_handle_view"
- android:layout_width="48dp"
- android:layout_height="4dp"
- android:layout_marginTop="8dp"
- android:layout_marginBottom="8dp"
- android:layout_gravity="center_horizontal|bottom"
- android:background="@drawable/qs_footer_drag_handle" />
-
</com.android.systemui.qs.QSFooterImpl>
diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/qs_media_panel.xml
index d633ff4..9ad380d 100644
--- a/packages/SystemUI/res/layout/qs_media_panel.xml
+++ b/packages/SystemUI/res/layout/qs_media_panel.xml
@@ -16,236 +16,173 @@
-->
<!-- Layout for media controls inside QSPanel carousel -->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/qs_media_controls"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
android:gravity="center_horizontal|fill_vertical"
- android:paddingTop="@dimen/qs_media_panel_outer_padding"
- android:paddingBottom="@dimen/qs_media_panel_outer_padding"
- android:background="@drawable/qs_media_background"
- >
+ app:layoutDescription="@xml/media_scene">
+
+ <View
+ android:id="@+id/media_background"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="@drawable/qs_media_background"
+ app:layout_constraintEnd_toEndOf="@id/view_width"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ />
+
+ <FrameLayout
+ android:id="@+id/notification_media_progress_time"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:forceHasOverlappingRendering="false">
+ <!-- width is set to "match_parent" to avoid extra layout calls -->
+ <TextView
+ android:id="@+id/media_elapsed_time"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:gravity="left"
+ android:textSize="14sp" />
+
+ <TextView
+ android:id="@+id/media_total_time"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:gravity="right"
+ android:textSize="14sp" />
+ </FrameLayout>
+
+ <ImageButton
+ android:id="@+id/action0"
+ style="@style/MediaPlayer.Button"
+ android:layout_width="48dp"
+ android:layout_height="48dp" />
+
+ <ImageButton
+ android:id="@+id/action1"
+ style="@style/MediaPlayer.Button"
+ android:layout_width="48dp"
+ android:layout_height="48dp" />
+
+ <ImageButton
+ android:id="@+id/action2"
+ style="@style/MediaPlayer.Button"
+ android:layout_width="52dp"
+ android:layout_height="52dp" />
+
+ <ImageButton
+ android:id="@+id/action3"
+ style="@style/MediaPlayer.Button"
+ android:layout_width="48dp"
+ android:layout_height="48dp" />
+
+ <ImageButton
+ android:id="@+id/action4"
+ style="@style/MediaPlayer.Button"
+ android:layout_width="48dp"
+ android:layout_height="48dp" />
+
+ <!-- Album Art -->
+ <ImageView
+ android:id="@+id/album_art"
+ android:layout_width="@dimen/qs_media_album_size"
+ android:layout_height="@dimen/qs_media_album_size" />
+
+ <!-- Seamless Output Switcher -->
+ <LinearLayout
+ android:id="@+id/media_seamless"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:background="@*android:drawable/media_seamless_background"
+ android:orientation="horizontal"
+ android:forceHasOverlappingRendering="false"
+ android:paddingLeft="12dp"
+ android:paddingTop="6dp"
+ android:paddingRight="12dp"
+ android:paddingBottom="6dp">
+
+ <ImageView
+ android:id="@+id/media_seamless_image"
+ android:layout_width="@dimen/qs_seamless_icon_size"
+ android:layout_height="@dimen/qs_seamless_icon_size"
+ android:layout_marginRight="8dp"
+ android:src="@*android:drawable/ic_media_seamless" />
+
+ <TextView
+ android:id="@+id/media_seamless_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:singleLine="true"
+ android:text="@*android:string/ext_media_seamless_action"
+ android:textSize="14sp" />
+ </LinearLayout>
+
+ <!-- Seek Bar -->
+ <SeekBar
+ android:id="@+id/media_progress_bar"
+ style="@android:style/Widget.ProgressBar.Horizontal"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:maxHeight="3dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp"
+ android:splitTrack="false" />
+
+ <!-- App name -->
+ <TextView
+ android:id="@+id/app_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textSize="14sp" />
+
+ <!-- Song name -->
+ <TextView
+ android:id="@+id/header_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:singleLine="true"
+ android:textSize="18sp" />
+
+ <!-- Artist name -->
+ <TextView
+ android:id="@+id/header_artist"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:singleLine="true"
+ android:textSize="14sp" />
+
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/icon"
+ android:layout_width="16dp"
+ android:layout_height="16dp" />
<!-- Buttons to remove this view when no longer needed -->
<include
layout="@layout/qs_media_panel_options"
- android:visibility="gone"/>
+ android:visibility="gone"
+ app:layout_constraintEnd_toEndOf="@id/view_width"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
- <LinearLayout
- android:id="@+id/media_guts"
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/view_width"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <!-- Header section -->
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
- android:paddingStart="@dimen/qs_media_panel_outer_padding"
- android:paddingEnd="16dp"
- >
-
- <ImageView
- android:id="@+id/album_art"
- android:layout_width="@dimen/qs_media_album_size"
- android:layout_height="@dimen/qs_media_album_size"
- android:layout_marginRight="16dp"
- android:layout_weight="0"
- />
-
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- >
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- >
- <com.android.internal.widget.CachingIconView
- android:id="@+id/icon"
- android:layout_width="16dp"
- android:layout_height="16dp"
- android:layout_marginEnd="5dp"
- />
- <TextView
- android:id="@+id/app_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="14sp"
- android:singleLine="true"
- />
- </LinearLayout>
-
- <!-- Song name -->
- <TextView
- android:id="@+id/header_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textSize="18sp"
- android:paddingBottom="6dp"
- android:gravity="center"/>
-
- <!-- Artist name -->
- <TextView
- android:id="@+id/header_artist"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:fontFamily="@*android:string/config_bodyFontFamily"
- android:textSize="14sp"
- android:singleLine="true"
- />
- </LinearLayout>
-
- <!-- Output chip -->
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:visibility="gone"
- android:paddingTop="6dp"
- android:paddingBottom="6dp"
- android:paddingLeft="12dp"
- android:paddingRight="12dp"
- android:gravity="center"
- android:id="@+id/media_seamless"
- android:background="@*android:drawable/media_seamless_background"
- android:layout_weight="1"
- android:forceHasOverlappingRendering="false"
- >
- <ImageView
- android:layout_width="@dimen/qs_seamless_icon_size"
- android:layout_height="@dimen/qs_seamless_icon_size"
- android:src="@*android:drawable/ic_media_seamless"
- android:layout_marginRight="8dp"
- android:id="@+id/media_seamless_image"
- />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:fontFamily="@*android:string/config_bodyFontFamily"
- android:text="@*android:string/ext_media_seamless_action"
- android:textSize="14sp"
- android:id="@+id/media_seamless_text"
- android:singleLine="true"
- />
- </LinearLayout>
- </LinearLayout>
-
- <!-- Seek Bar -->
- <SeekBar
- android:id="@+id/media_progress_bar"
- style="@android:style/Widget.ProgressBar.Horizontal"
- android:clickable="true"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:maxHeight="3dp"
- android:paddingTop="24dp"
- android:paddingBottom="24dp"
- android:layout_marginBottom="-24dp"
- android:layout_marginTop="-24dp"
- android:splitTrack="false"
- />
-
- <FrameLayout
- android:id="@+id/notification_media_progress_time"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="@dimen/qs_media_panel_outer_padding"
- android:paddingEnd="@dimen/qs_media_panel_outer_padding"
- android:layout_marginBottom="10dp"
- android:layout_gravity="center"
- >
- <!-- width is set to "match_parent" to avoid extra layout calls -->
- <TextView
- android:id="@+id/media_elapsed_time"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:fontFamily="@*android:string/config_bodyFontFamily"
- android:textSize="14sp"
- android:gravity="left"
- />
- <TextView
- android:id="@+id/media_total_time"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:fontFamily="@*android:string/config_bodyFontFamily"
- android:layout_alignParentRight="true"
- android:textSize="14sp"
- android:gravity="right"
- />
- </FrameLayout>
-
- <!-- Controls -->
- <LinearLayout
- android:id="@+id/media_actions"
- android:orientation="horizontal"
- android:layoutDirection="ltr"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="@dimen/qs_media_panel_outer_padding"
- android:paddingEnd="@dimen/qs_media_panel_outer_padding"
- android:gravity="center"
- >
- <ImageButton
- style="@style/MediaPlayer.Button"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginStart="8dp"
- android:layout_marginEnd="8dp"
- android:gravity="center"
- android:visibility="gone"
- android:id="@+id/action0"
- />
- <ImageButton
- style="@style/MediaPlayer.Button"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginStart="8dp"
- android:layout_marginEnd="8dp"
- android:gravity="center"
- android:visibility="gone"
- android:id="@+id/action1"
- />
- <ImageButton
- style="@style/MediaPlayer.Button"
- android:layout_width="52dp"
- android:layout_height="52dp"
- android:layout_marginStart="8dp"
- android:layout_marginEnd="8dp"
- android:gravity="center"
- android:visibility="gone"
- android:id="@+id/action2"
- />
- <ImageButton
- style="@style/MediaPlayer.Button"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginStart="8dp"
- android:layout_marginEnd="8dp"
- android:gravity="center"
- android:visibility="gone"
- android:id="@+id/action3"
- />
- <ImageButton
- style="@style/MediaPlayer.Button"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginStart="8dp"
- android:layout_marginEnd="8dp"
- android:gravity="center"
- android:visibility="gone"
- android:id="@+id/action4"
- />
- </LinearLayout>
- </LinearLayout>
-</LinearLayout>
+ app:layout_constraintGuide_begin="300dp" />
+</androidx.constraintlayout.motion.widget.MotionLayout>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 01dfeb2..cdf8426 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -54,20 +54,32 @@
android:layout_marginTop="@*android:dimen/quick_qs_offset_height"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/qs_footer_height"
android:elevation="4dp"
android:background="@android:color/transparent"
android:focusable="true"
- android:accessibilityTraversalBefore="@android:id/edit"
- />
+ android:accessibilityTraversalBefore="@android:id/edit">
+ <include layout="@layout/qs_footer_impl" />
+ </com.android.systemui.qs.QSPanel>
<include layout="@layout/quick_status_bar_expanded_header" />
- <include layout="@layout/qs_footer_impl" />
-
<include android:id="@+id/qs_detail" layout="@layout/qs_detail" />
<include android:id="@+id/qs_customize" layout="@layout/qs_customize_panel"
android:visibility="gone" />
+ <FrameLayout
+ android:id="@+id/qs_drag_handle_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:elevation="4dp"
+ android:paddingBottom="5dp">
+ <View
+ android:layout_width="46dp"
+ android:layout_height="3dp"
+ android:background="@drawable/qs_footer_drag_handle" />
+ </FrameLayout>
+
+
</com.android.systemui.qs.QSContainerImpl>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index e99b917..9a7c344 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -20,7 +20,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/header"
android:layout_width="match_parent"
- android:layout_height="@*android:dimen/quick_qs_total_height"
+ android:layout_height="wrap_content"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:background="@android:color/transparent"
android:baselineAligned="false"
@@ -29,6 +29,7 @@
android:clipToPadding="false"
android:paddingTop="0dp"
android:paddingEnd="0dp"
+ android:paddingBottom="10dp"
android:paddingStart="0dp"
android:elevation="4dp" >
@@ -45,8 +46,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/quick_qs_status_icons"
- android:layout_marginStart="@dimen/qs_header_tile_margin_horizontal"
- android:layout_marginEnd="@dimen/qs_header_tile_margin_horizontal"
android:accessibilityTraversalAfter="@+id/date_time_group"
android:accessibilityTraversalBefore="@id/expand_indicator"
android:clipChildren="false"
@@ -54,15 +53,6 @@
android:focusable="true"
android:importantForAccessibility="yes" />
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/qs_detail_header_progress"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:alpha="0"
- android:background="@color/qs_detail_progress_track"
- android:src="@drawable/indeterminate_anim"/>
-
<TextView
android:id="@+id/header_debug_info"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 5a8c5dc..d118d89 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -40,4 +40,6 @@
<!-- Padding between status bar and bubbles when displayed in expanded state, smaller
value in landscape since we have limited vertical space-->
<dimen name="bubble_padding_top">4dp</dimen>
+
+ <dimen name="controls_activity_view_top_offset">25dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index b677600..ef146f6 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -240,8 +240,8 @@
<color name="magnification_border_color">#FF9900</color>
<!-- controls -->
- <color name="control_primary_text">@color/GM2_grey_100</color>
- <color name="control_secondary_text">@color/GM2_grey_500</color>
+ <color name="control_primary_text">#E6FFFFFF</color>
+ <color name="control_secondary_text">#99FFFFFF</color>
<color name="control_default_foreground">@color/GM2_grey_500</color>
<color name="control_default_background">@color/GM2_grey_900</color>
<color name="control_list_popup_background">@*android:color/background_floating_material_dark</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 221a64a..4984651 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -498,6 +498,7 @@
<dimen name="qs_quick_tile_padding">12dp</dimen>
<dimen name="qs_header_gear_translation">16dp</dimen>
<dimen name="qs_header_tile_margin_horizontal">4dp</dimen>
+ <dimen name="qs_header_tile_margin_bottom">18dp</dimen>
<dimen name="qs_page_indicator_width">16dp</dimen>
<dimen name="qs_page_indicator_height">8dp</dimen>
<dimen name="qs_tile_icon_size">24dp</dimen>
@@ -1043,6 +1044,10 @@
<dimen name="bottom_padding">48dp</dimen>
<dimen name="edge_margin">8dp</dimen>
+ <!-- The absolute side margins of quick settings -->
+ <dimen name="quick_settings_side_margins">16dp</dimen>
+ <dimen name="quick_settings_expanded_bottom_margin">16dp</dimen>
+ <dimen name="quick_settings_media_extra_bottom_margin">4dp</dimen>
<dimen name="rounded_corner_content_padding">0dp</dimen>
<dimen name="nav_content_padding">0dp</dimen>
<dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen>
@@ -1230,12 +1235,12 @@
<!-- Size of media cards in the QSPanel carousel -->
<dimen name="qs_media_width">350dp</dimen>
- <dimen name="qs_media_padding">8dp</dimen>
+ <dimen name="qs_media_padding">16dp</dimen>
<dimen name="qs_media_panel_outer_padding">16dp</dimen>
- <dimen name="qs_media_corner_radius">10dp</dimen>
- <dimen name="qs_media_album_size">72dp</dimen>
+ <dimen name="qs_media_album_size">52dp</dimen>
<dimen name="qs_seamless_icon_size">20dp</dimen>
<dimen name="qqs_media_spacing">8dp</dimen>
+ <dimen name="qqs_horizonal_tile_padding_bottom">8dp</dimen>
<dimen name="magnification_border_size">5dp</dimen>
<dimen name="magnification_frame_move_short">5dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 8156e8d..76ca385 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -70,6 +70,26 @@
<item type="id" name="panel_alpha_animator_end_tag"/>
<item type="id" name="cross_fade_layer_type_changed_tag"/>
+ <item type="id" name="absolute_x_animator_tag"/>
+ <item type="id" name="absolute_x_animator_start_tag"/>
+ <item type="id" name="absolute_x_animator_end_tag"/>
+ <item type="id" name="absolute_x_current_value"/>
+
+ <item type="id" name="absolute_y_animator_tag"/>
+ <item type="id" name="absolute_y_animator_start_tag"/>
+ <item type="id" name="absolute_y_animator_end_tag"/>
+ <item type="id" name="absolute_y_current_value"/>
+
+ <item type="id" name="view_height_animator_tag"/>
+ <item type="id" name="view_height_animator_start_tag"/>
+ <item type="id" name="view_height_animator_end_tag"/>
+ <item type="id" name="view_height_current_value"/>
+
+ <item type="id" name="view_width_animator_tag"/>
+ <item type="id" name="view_width_animator_start_tag"/>
+ <item type="id" name="view_width_animator_end_tag"/>
+ <item type="id" name="view_width_current_value"/>
+
<!-- Whether the icon is from a notification for which targetSdk < L -->
<item type="id" name="icon_is_pre_L"/>
diff --git a/packages/SystemUI/res/xml/media_scene.xml b/packages/SystemUI/res/xml/media_scene.xml
new file mode 100644
index 0000000..f61b2b0
--- /dev/null
+++ b/packages/SystemUI/res/xml/media_scene.xml
@@ -0,0 +1,447 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<MotionScene
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <Transition
+ app:constraintSetStart="@id/collapsed"
+ app:constraintSetEnd="@id/expanded"
+ app:duration="1000" >
+ <KeyFrameSet >
+ <KeyPosition
+ app:motionTarget="@+id/action0"
+ app:keyPositionType="pathRelative"
+ app:framePosition="70"
+ app:sizePercent="0.9" />
+ <KeyPosition
+ app:motionTarget="@+id/action1"
+ app:keyPositionType="pathRelative"
+ app:framePosition="70"
+ app:sizePercent="0.9" />
+ <KeyPosition
+ app:motionTarget="@+id/action2"
+ app:keyPositionType="pathRelative"
+ app:framePosition="70"
+ app:sizePercent="0.9" />
+ <KeyPosition
+ app:motionTarget="@+id/action3"
+ app:keyPositionType="pathRelative"
+ app:framePosition="70"
+ app:sizePercent="0.9" />
+ <KeyPosition
+ app:motionTarget="@+id/action4"
+ app:keyPositionType="pathRelative"
+ app:framePosition="70"
+ app:sizePercent="0.9" />
+ <KeyPosition
+ app:motionTarget="@+id/media_progress_bar"
+ app:keyPositionType="pathRelative"
+ app:framePosition="70"
+ app:sizePercent="0.9" />
+ <KeyAttribute
+ app:motionTarget="@id/media_progress_bar"
+ app:framePosition="0"
+ android:alpha="0.0" />
+ <KeyAttribute
+ app:motionTarget="@+id/media_progress_bar"
+ app:framePosition="70"
+ android:alpha="0.0"/>
+ <KeyPosition
+ app:motionTarget="@+id/notification_media_progress_time"
+ app:keyPositionType="pathRelative"
+ app:framePosition="70"
+ app:sizePercent="0.9" />
+ <KeyAttribute
+ app:motionTarget="@id/notification_media_progress_time"
+ app:framePosition="0"
+ android:alpha="0.0" />
+ <KeyAttribute
+ app:motionTarget="@+id/notification_media_progress_time"
+ app:framePosition="70"
+ android:alpha="0.0"/>
+ <KeyAttribute
+ app:motionTarget="@id/action0"
+ app:framePosition="0"
+ android:alpha="0.0" />
+ <KeyAttribute
+ app:motionTarget="@+id/action0"
+ app:framePosition="70"
+ android:alpha="0.0"/>
+ <KeyAttribute
+ app:motionTarget="@id/action1"
+ app:framePosition="0"
+ android:alpha="0.0" />
+ <KeyAttribute
+ app:motionTarget="@+id/action1"
+ app:framePosition="70"
+ android:alpha="0.0"/>
+ <KeyAttribute
+ app:motionTarget="@id/action2"
+ app:framePosition="0"
+ android:alpha="0.0" />
+ <KeyAttribute
+ app:motionTarget="@+id/action2"
+ app:framePosition="70"
+ android:alpha="0.0"/>
+ <KeyAttribute
+ app:motionTarget="@id/action3"
+ app:framePosition="0"
+ android:alpha="0.0" />
+ <KeyAttribute
+ app:motionTarget="@+id/action3"
+ app:framePosition="70"
+ android:alpha="0.0"/>
+ <KeyAttribute
+ app:motionTarget="@id/action4"
+ app:framePosition="0"
+ android:alpha="0.0" />
+ <KeyAttribute
+ app:motionTarget="@+id/action4"
+ app:framePosition="70"
+ android:alpha="0.0"/>
+ </KeyFrameSet>
+ </Transition>
+
+ <ConstraintSet android:id="@+id/expanded">
+ <Constraint
+ android:id="@+id/icon"
+ android:layout_width="16dp"
+ android:layout_height="16dp"
+ android:layout_marginStart="18dp"
+ android:layout_marginTop="22dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ />
+
+ <Constraint
+ android:id="@+id/app_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="10dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginTop="20dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toEndOf="@id/icon"
+ app:layout_constraintEnd_toStartOf="@id/media_seamless"
+ app:layout_constraintHorizontal_bias="0"
+ />
+
+ <Constraint
+ android:id="@+id/media_seamless"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ app:layout_constraintEnd_toEndOf="@id/view_width"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintWidth_min="60dp"
+ android:layout_marginTop="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+ />
+
+ <Constraint
+ android:id="@+id/album_art"
+ android:layout_width="@dimen/qs_media_album_size"
+ android:layout_height="@dimen/qs_media_album_size"
+ android:layout_marginTop="14dp"
+ android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintTop_toBottomOf="@+id/app_name"
+ app:layout_constraintStart_toStartOf="parent"
+ />
+
+ <!-- Song name -->
+ <Constraint
+ android:id="@+id/header_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginTop="17dp"
+ android:layout_marginStart="16dp"
+ app:layout_constraintTop_toBottomOf="@+id/app_name"
+ app:layout_constraintStart_toEndOf="@id/album_art"
+ app:layout_constraintEnd_toEndOf="@id/view_width"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <!-- Artist name -->
+ <Constraint
+ android:id="@+id/header_artist"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginTop="3dp"
+ app:layout_constraintTop_toBottomOf="@id/header_title"
+ app:layout_constraintStart_toStartOf="@id/header_title"
+ app:layout_constraintEnd_toEndOf="@id/view_width"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <!-- Seek Bar -->
+ <Constraint
+ android:id="@+id/media_progress_bar"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="3dp"
+ app:layout_constraintTop_toBottomOf="@id/header_artist"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="@id/view_width"
+ />
+
+ <Constraint
+ android:id="@+id/notification_media_progress_time"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="38dp"
+ android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintTop_toBottomOf="@id/header_artist"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="@id/view_width"
+ />
+
+ <Constraint
+ android:id="@+id/action0"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginTop="5dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toLeftOf="@id/action1"
+ app:layout_constraintTop_toBottomOf="@id/notification_media_progress_time"
+ app:layout_constraintBottom_toBottomOf="parent">
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action1"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintLeft_toRightOf="@id/action0"
+ app:layout_constraintRight_toLeftOf="@id/action2"
+ app:layout_constraintTop_toTopOf="@id/action0"
+ app:layout_constraintBottom_toBottomOf="parent">
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action2"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintLeft_toRightOf="@id/action1"
+ app:layout_constraintRight_toLeftOf="@id/action3"
+ app:layout_constraintTop_toTopOf="@id/action0"
+ app:layout_constraintBottom_toBottomOf="parent">
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action3"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ app:layout_constraintLeft_toRightOf="@id/action2"
+ app:layout_constraintRight_toLeftOf="@id/action4"
+ app:layout_constraintTop_toTopOf="@id/action0"
+ android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintBottom_toBottomOf="parent">
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action4"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintLeft_toRightOf="@id/action3"
+ app:layout_constraintRight_toRightOf="@id/view_width"
+ app:layout_constraintTop_toTopOf="@id/action0"
+ app:layout_constraintBottom_toBottomOf="parent">
+ </Constraint>
+ </ConstraintSet>
+
+ <ConstraintSet android:id="@+id/collapsed">
+ <Constraint
+ android:id="@+id/icon"
+ android:layout_width="16dp"
+ android:layout_height="16dp"
+ android:layout_marginStart="18dp"
+ android:layout_marginTop="22dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ />
+
+ <Constraint
+ android:id="@+id/app_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="10dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginTop="20dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toEndOf="@id/icon"
+ app:layout_constraintEnd_toStartOf="@id/media_seamless"
+ app:layout_constraintHorizontal_bias="0"
+ />
+
+ <Constraint
+ android:id="@+id/media_seamless"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ app:layout_constraintEnd_toEndOf="@id/view_width"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintWidth_min="60dp"
+ android:layout_marginTop="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+ />
+
+ <Constraint
+ android:id="@+id/album_art"
+ android:layout_width="@dimen/qs_media_album_size"
+ android:layout_height="@dimen/qs_media_album_size"
+ android:layout_marginTop="16dp"
+ android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginBottom="24dp"
+ app:layout_constraintTop_toBottomOf="@id/icon"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ />
+
+ <!-- Song name -->
+ <Constraint
+ android:id="@+id/header_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="17dp"
+ android:layout_marginStart="16dp"
+ app:layout_constraintTop_toBottomOf="@id/app_name"
+ app:layout_constraintBottom_toTopOf="@id/header_artist"
+ app:layout_constraintStart_toEndOf="@id/album_art"
+ app:layout_constraintEnd_toStartOf="@id/action0"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <!-- Artist name -->
+ <Constraint
+ android:id="@+id/header_artist"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="3dp"
+ android:layout_marginBottom="24dp"
+ app:layout_constraintTop_toBottomOf="@id/header_title"
+ app:layout_constraintStart_toStartOf="@id/header_title"
+ app:layout_constraintEnd_toStartOf="@id/action0"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <!-- Seek Bar -->
+ <Constraint
+ android:id="@+id/media_progress_bar"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:alpha="0.0"
+ app:layout_constraintTop_toBottomOf="@id/album_art"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="@id/view_width"
+ android:visibility="gone"
+ />
+
+ <Constraint
+ android:id="@+id/notification_media_progress_time"
+ android:alpha="0.0"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="35dp"
+ android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintTop_toBottomOf="@id/album_art"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="@id/view_width"
+ android:visibility="gone"
+ />
+
+ <Constraint
+ android:id="@+id/action0"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginTop="16dp"
+ android:visibility="gone"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintTop_toBottomOf="@id/app_name"
+ app:layout_constraintLeft_toRightOf="@id/header_title"
+ app:layout_constraintRight_toLeftOf="@id/action1"
+ >
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action1"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginTop="18dp"
+ app:layout_constraintTop_toBottomOf="@id/app_name"
+ app:layout_constraintLeft_toRightOf="@id/action0"
+ app:layout_constraintRight_toLeftOf="@id/action2"
+ >
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action2"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginTop="18dp"
+ app:layout_constraintTop_toBottomOf="@id/app_name"
+ app:layout_constraintLeft_toRightOf="@id/action1"
+ app:layout_constraintRight_toLeftOf="@id/action3"
+ >
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action3"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginTop="18dp"
+ app:layout_constraintTop_toBottomOf="@id/app_name"
+ app:layout_constraintLeft_toRightOf="@id/action2"
+ app:layout_constraintRight_toLeftOf="@id/action4"
+ >
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action4"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:visibility="gone"
+ android:layout_marginTop="18dp"
+ app:layout_constraintTop_toBottomOf="@id/app_name"
+ app:layout_constraintLeft_toRightOf="@id/action3"
+ app:layout_constraintRight_toRightOf="@id/view_width"
+ >
+ </Constraint>
+ </ConstraintSet>
+</MotionScene>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMedia.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardMedia.kt
deleted file mode 100644
index 487c295..0000000
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMedia.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard
-
-import android.graphics.drawable.Drawable
-
-import java.util.List
-
-/** State for lock screen media controls. */
-data class KeyguardMedia(
- val foregroundColor: Int,
- val backgroundColor: Int,
- val app: String?,
- val appIcon: Drawable?,
- val artist: String?,
- val song: String?,
- val artwork: Drawable?,
- val actionIcons: List<Drawable>
-)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
deleted file mode 100644
index af5196f..0000000
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.media.MediaMetadata;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.core.graphics.drawable.RoundedBitmapDrawable;
-import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.Observer;
-import androidx.palette.graphics.Palette;
-
-import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.media.MediaControllerFactory;
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.stack.MediaHeaderView;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Media controls to display on the lockscreen
- *
- * TODO: Should extend MediaControlPanel to avoid code duplication.
- * Unfortunately, it isn't currently possible because the ActivatableNotificationView background is
- * different.
- */
-@Singleton
-public class KeyguardMediaPlayer {
-
- private static final String TAG = "KeyguardMediaPlayer";
- // Buttons that can be displayed on lock screen media controls.
- private static final int[] ACTION_IDS = {R.id.action0, R.id.action1, R.id.action2};
-
- private final Context mContext;
- private final Executor mBackgroundExecutor;
- private final KeyguardMediaViewModel mViewModel;
- private KeyguardMediaObserver mObserver;
-
- @Inject
- public KeyguardMediaPlayer(Context context, MediaControllerFactory factory,
- @Background Executor backgroundExecutor) {
- mContext = context;
- mBackgroundExecutor = backgroundExecutor;
- mViewModel = new KeyguardMediaViewModel(context, factory);
- }
-
- /** Binds media controls to a view hierarchy. */
- public void bindView(View v) {
- if (mObserver != null) {
- throw new IllegalStateException("cannot bind views, already bound");
- }
- mViewModel.loadDimens();
- mObserver = new KeyguardMediaObserver(v);
- // Control buttons
- for (int i = 0; i < ACTION_IDS.length; i++) {
- ImageButton button = v.findViewById(ACTION_IDS[i]);
- if (button == null) {
- continue;
- }
- final int index = i;
- button.setOnClickListener(unused -> mViewModel.onActionClick(index));
- }
- mViewModel.getKeyguardMedia().observeForever(mObserver);
- }
-
- /** Unbinds media controls. */
- public void unbindView() {
- if (mObserver == null) {
- throw new IllegalStateException("cannot unbind views, nothing bound");
- }
- mViewModel.getKeyguardMedia().removeObserver(mObserver);
- mObserver = null;
- }
-
- /** Clear the media controls because there isn't an active session. */
- public void clearControls() {
- mBackgroundExecutor.execute(mViewModel::clearControls);
- }
-
- /**
- * Update the media player
- *
- * TODO: consider registering a MediaLister instead of exposing this update method.
- *
- * @param entry Media notification that will be used to update the player
- * @param appIcon Icon for the app playing the media
- * @param mediaMetadata Media metadata that will be used to update the player
- */
- public void updateControls(NotificationEntry entry, Icon appIcon,
- MediaMetadata mediaMetadata) {
- if (mObserver == null) {
- throw new IllegalStateException("cannot update controls, views not bound");
- }
- if (mediaMetadata == null) {
- Log.d(TAG, "media metadata was null, closing media controls");
- // Note that clearControls() executes on the same background executor, so there
- // shouldn't be an issue with an outdated update running after clear. However, if stale
- // controls are observed then consider removing any enqueued updates.
- clearControls();
- return;
- }
- mBackgroundExecutor.execute(() -> mViewModel.updateControls(entry, appIcon, mediaMetadata));
- }
-
- /** ViewModel for KeyguardMediaControls. */
- private static final class KeyguardMediaViewModel {
-
- private final Context mContext;
- private final MediaControllerFactory mMediaControllerFactory;
- private final MutableLiveData<KeyguardMedia> mMedia = new MutableLiveData<>();
- private final Object mActionsLock = new Object();
- private List<PendingIntent> mActions;
- private float mAlbumArtRadius;
- private int mAlbumArtSize;
-
- KeyguardMediaViewModel(Context context, MediaControllerFactory factory) {
- mContext = context;
- mMediaControllerFactory = factory;
- loadDimens();
- }
-
- /** Close the media player because there isn't an active session. */
- public void clearControls() {
- synchronized (mActionsLock) {
- mActions = null;
- }
- mMedia.postValue(null);
- }
-
- /** Update the media player with information about the active session. */
- public void updateControls(NotificationEntry entry, Icon appIcon,
- MediaMetadata mediaMetadata) {
-
- // Check the playback state of the media controller. If it is null, then the session was
- // probably destroyed. Don't update in this case.
- final MediaSession.Token token = entry.getSbn().getNotification().extras
- .getParcelable(Notification.EXTRA_MEDIA_SESSION);
- final MediaController controller = token != null
- ? mMediaControllerFactory.create(token) : null;
- if (controller != null && controller.getPlaybackState() == null) {
- clearControls();
- return;
- }
-
- // Foreground and Background colors computed from album art
- Notification notif = entry.getSbn().getNotification();
- int fgColor = notif.color;
- int bgColor = entry.getRow() == null ? -1 : entry.getRow().getCurrentBackgroundTint();
- Bitmap artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
- if (artworkBitmap == null) {
- artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
- }
- if (artworkBitmap != null) {
- // If we have art, get colors from that
- Palette p = MediaNotificationProcessor.generateArtworkPaletteBuilder(artworkBitmap)
- .generate();
- Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(p);
- bgColor = swatch.getRgb();
- fgColor = MediaNotificationProcessor.selectForegroundColor(bgColor, p);
- }
- // Make sure colors will be legible
- boolean isDark = !ContrastColorUtil.isColorLight(bgColor);
- fgColor = ContrastColorUtil.resolveContrastColor(mContext, fgColor, bgColor,
- isDark);
- fgColor = ContrastColorUtil.ensureTextContrast(fgColor, bgColor, isDark);
-
- // Album art
- RoundedBitmapDrawable artwork = null;
- if (artworkBitmap != null) {
- Bitmap original = artworkBitmap.copy(Bitmap.Config.ARGB_8888, true);
- Bitmap scaled = Bitmap.createScaledBitmap(original, mAlbumArtSize, mAlbumArtSize,
- false);
- artwork = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled);
- artwork.setCornerRadius(mAlbumArtRadius);
- }
-
- // App name
- Notification.Builder builder = Notification.Builder.recoverBuilder(mContext, notif);
- String app = builder.loadHeaderAppName();
-
- // App Icon
- Drawable appIconDrawable = appIcon.loadDrawable(mContext);
-
- // Song name
- String song = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
-
- // Artist name
- String artist = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
-
- // Control buttons
- List<Drawable> actionIcons = new ArrayList<>();
- final List<PendingIntent> intents = new ArrayList<>();
- Notification.Action[] actions = notif.actions;
- final int[] actionsToShow = notif.extras.getIntArray(
- Notification.EXTRA_COMPACT_ACTIONS);
-
- Context packageContext = entry.getSbn().getPackageContext(mContext);
- for (int i = 0; i < ACTION_IDS.length; i++) {
- if (actionsToShow != null && actions != null && i < actionsToShow.length
- && actionsToShow[i] < actions.length) {
- final int idx = actionsToShow[i];
- actionIcons.add(actions[idx].getIcon().loadDrawable(packageContext));
- intents.add(actions[idx].actionIntent);
- } else {
- actionIcons.add(null);
- intents.add(null);
- }
- }
- synchronized (mActionsLock) {
- mActions = intents;
- }
-
- KeyguardMedia data = new KeyguardMedia(fgColor, bgColor, app, appIconDrawable, artist,
- song, artwork, actionIcons);
- mMedia.postValue(data);
- }
-
- /** Gets state for the lock screen media controls. */
- public LiveData<KeyguardMedia> getKeyguardMedia() {
- return mMedia;
- }
-
- /**
- * Handle user clicks on media control buttons (actions).
- *
- * @param index position of the button that was clicked.
- */
- public void onActionClick(int index) {
- PendingIntent intent = null;
- // This might block the ui thread to wait for the lock. Currently, however, the
- // lock is held by the bg thread to assign a member, which should be fast. An
- // alternative could be to add the intents to the state and let the observer set
- // the onClick listeners.
- synchronized (mActionsLock) {
- if (mActions != null && index < mActions.size()) {
- intent = mActions.get(index);
- }
- }
- if (intent != null) {
- try {
- intent.send();
- } catch (PendingIntent.CanceledException e) {
- Log.d(TAG, "failed to send action intent", e);
- }
- }
- }
-
- void loadDimens() {
- mAlbumArtRadius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
- mAlbumArtSize = (int) mContext.getResources().getDimension(
- R.dimen.qs_media_album_size);
- }
- }
-
- /** Observer for state changes of lock screen media controls. */
- private static final class KeyguardMediaObserver implements Observer<KeyguardMedia> {
-
- private final View mRootView;
- private final MediaHeaderView mMediaHeaderView;
- private final ImageView mAlbumView;
- private final ImageView mAppIconView;
- private final TextView mAppNameView;
- private final TextView mTitleView;
- private final TextView mArtistView;
- private final List<ImageButton> mButtonViews = new ArrayList<>();
-
- KeyguardMediaObserver(View v) {
- mRootView = v;
- mMediaHeaderView = v instanceof MediaHeaderView ? (MediaHeaderView) v : null;
- mAlbumView = v.findViewById(R.id.album_art);
- mAppIconView = v.findViewById(R.id.icon);
- mAppNameView = v.findViewById(R.id.app_name);
- mTitleView = v.findViewById(R.id.header_title);
- mArtistView = v.findViewById(R.id.header_artist);
- for (int i = 0; i < ACTION_IDS.length; i++) {
- mButtonViews.add(v.findViewById(ACTION_IDS[i]));
- }
- }
-
- /** Updates lock screen media player views when state changes. */
- @Override
- public void onChanged(KeyguardMedia data) {
- if (data == null) {
- mRootView.setVisibility(View.GONE);
- return;
- }
- mRootView.setVisibility(View.VISIBLE);
-
- // Background color
- if (mMediaHeaderView != null) {
- mMediaHeaderView.setBackgroundColor(data.getBackgroundColor());
- }
-
- // Album art
- if (mAlbumView != null) {
- mAlbumView.setImageDrawable(data.getArtwork());
- mAlbumView.setVisibility(data.getArtwork() == null ? View.GONE : View.VISIBLE);
- }
-
- // App icon
- if (mAppIconView != null) {
- Drawable iconDrawable = data.getAppIcon();
- iconDrawable.setTint(data.getForegroundColor());
- mAppIconView.setImageDrawable(iconDrawable);
- }
-
- // App name
- if (mAppNameView != null) {
- String appNameString = data.getApp();
- mAppNameView.setText(appNameString);
- mAppNameView.setTextColor(data.getForegroundColor());
- }
-
- // Song name
- if (mTitleView != null) {
- mTitleView.setText(data.getSong());
- mTitleView.setTextColor(data.getForegroundColor());
- }
-
- // Artist name
- if (mArtistView != null) {
- mArtistView.setText(data.getArtist());
- mArtistView.setTextColor(data.getForegroundColor());
- }
-
- // Control buttons
- for (int i = 0; i < ACTION_IDS.length; i++) {
- ImageButton button = mButtonViews.get(i);
- if (button == null) {
- continue;
- }
- Drawable icon = data.getActionIcons().get(i);
- if (icon == null) {
- button.setVisibility(View.GONE);
- button.setImageDrawable(null);
- } else {
- button.setVisibility(View.VISIBLE);
- button.setImageDrawable(icon);
- button.setImageTintList(ColorStateList.valueOf(data.getForegroundColor()));
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 58a233d..50c4c51 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -33,6 +33,7 @@
import android.annotation.AnyThread;
import android.annotation.MainThread;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
@@ -248,8 +249,7 @@
// Battery status
private BatteryStatus mBatteryStatus;
- @VisibleForTesting
- protected StrongAuthTracker mStrongAuthTracker;
+ private StrongAuthTracker mStrongAuthTracker;
private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
mCallbacks = Lists.newArrayList();
@@ -1513,6 +1513,16 @@
mUserTrustIsUsuallyManaged.delete(userId);
}
+ @VisibleForTesting
+ protected void setStrongAuthTracker(@NonNull StrongAuthTracker tracker) {
+ if (mStrongAuthTracker != null) {
+ mLockPatternUtils.unregisterStrongAuthTracker(mStrongAuthTracker);
+ }
+
+ mStrongAuthTracker = tracker;
+ mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
+ }
+
private void registerRingerTracker() {
mRingerModeTracker.getRingerMode().observeForever(mRingerModeObserver);
}
@@ -1526,7 +1536,8 @@
DumpManager dumpManager,
RingerModeTracker ringerModeTracker,
@Background Executor backgroundExecutor,
- StatusBarStateController statusBarStateController) {
+ StatusBarStateController statusBarStateController,
+ LockPatternUtils lockPatternUtils) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
@@ -1535,6 +1546,7 @@
mBroadcastDispatcher = broadcastDispatcher;
mRingerModeTracker = ringerModeTracker;
mStatusBarStateController = statusBarStateController;
+ mLockPatternUtils = lockPatternUtils;
dumpManager.registerDumpable(getClass().getName(), this);
mHandler = new Handler(mainLooper) {
@@ -1703,8 +1715,8 @@
mTrustManager = context.getSystemService(TrustManager.class);
mTrustManager.registerTrustListener(this);
- mLockPatternUtils = new LockPatternUtils(context);
- mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
+
+ setStrongAuthTracker(mStrongAuthTracker);
mDreamManager = IDreamManager.Stub.asInterface(
ServiceManager.getService(DreamService.DREAM_SERVICE));
@@ -2910,6 +2922,9 @@
mBroadcastDispatcher.unregisterReceiver(mBroadcastAllReceiver);
mRingerModeTracker.getRingerMode().removeObserver(mRingerModeObserver);
+ mLockPatternUtils.unregisterStrongAuthTracker(mStrongAuthTracker);
+ mTrustManager.unregisterTrustListener(this);
+
mHandler.removeCallbacksAndMessages(null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index b727563..0135e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -128,6 +128,8 @@
public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT =
AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT; // 13
+ private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+
private Recents mRecents;
private StatusBar mStatusBar;
private SystemActionsBroadcastReceiver mReceiver;
@@ -147,7 +149,11 @@
@Override
public void start() {
- mContext.registerReceiverForAllUsers(mReceiver, mReceiver.createIntentFilter(), null, null);
+ mContext.registerReceiverForAllUsers(
+ mReceiver,
+ mReceiver.createIntentFilter(),
+ PERMISSION_SELF,
+ null);
registerActions();
}
@@ -397,6 +403,7 @@
case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER:
case INTENT_ACTION_ACCESSIBILITY_SHORTCUT: {
Intent intent = new Intent(intentAction);
+ intent.setPackage(context.getPackageName());
return PendingIntent.getBroadcast(context, 0, intent, 0);
}
default:
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index ca0cd77..6a5e94c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -175,7 +175,7 @@
private INotificationManager mINotificationManager;
// Callback that updates BubbleOverflowActivity on data change.
- @Nullable private BubbleData.Listener mOverflowListener = null;
+ @Nullable private Runnable mOverflowCallback = null;
private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private IStatusBarService mBarService;
@@ -280,31 +280,6 @@
}
}
- public BubbleController(Context context,
- NotificationShadeWindowController notificationShadeWindowController,
- StatusBarStateController statusBarStateController,
- ShadeController shadeController,
- BubbleData data,
- ConfigurationController configurationController,
- NotificationInterruptStateProvider interruptionStateProvider,
- ZenModeController zenModeController,
- NotificationLockscreenUserManager notifUserManager,
- NotificationGroupManager groupManager,
- NotificationEntryManager entryManager,
- NotifPipeline notifPipeline,
- FeatureFlags featureFlags,
- DumpManager dumpManager,
- FloatingContentCoordinator floatingContentCoordinator,
- BubbleDataRepository dataRepository,
- SysUiState sysUiState,
- INotificationManager notificationManager) {
- this(context, notificationShadeWindowController, statusBarStateController, shadeController,
- data, null /* synchronizer */, configurationController, interruptionStateProvider,
- zenModeController, notifUserManager, groupManager, entryManager,
- notifPipeline, featureFlags, dumpManager, floatingContentCoordinator,
- dataRepository, sysUiState, notificationManager);
- }
-
/**
* Injected constructor. See {@link BubbleModule}.
*/
@@ -326,7 +301,8 @@
FloatingContentCoordinator floatingContentCoordinator,
BubbleDataRepository dataRepository,
SysUiState sysUiState,
- INotificationManager notificationManager) {
+ INotificationManager notificationManager,
+ WindowManager windowManager) {
dumpManager.registerDumpable(TAG, this);
mContext = context;
mShadeController = shadeController;
@@ -395,7 +371,7 @@
}
mSurfaceSynchronizer = synchronizer;
- mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mWindowManager = windowManager;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -595,8 +571,8 @@
mInflateSynchronously = inflateSynchronously;
}
- void setOverflowListener(BubbleData.Listener listener) {
- mOverflowListener = listener;
+ void setOverflowCallback(Runnable updateOverflow) {
+ mOverflowCallback = updateOverflow;
}
/**
@@ -1010,8 +986,8 @@
@Override
public void applyUpdate(BubbleData.Update update) {
// Update bubbles in overflow.
- if (mOverflowListener != null) {
- mOverflowListener.applyUpdate(update);
+ if (mOverflowCallback != null) {
+ mOverflowCallback.run();
}
// Collapsing? Do this first before remaining steps.
@@ -1039,8 +1015,7 @@
if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey())
&& (!bubble.showInShade()
|| reason == DISMISS_NOTIF_CANCEL
- || reason == DISMISS_GROUP_CANCELLED
- || reason == DISMISS_OVERFLOW_MAX_REACHED)) {
+ || reason == DISMISS_GROUP_CANCELLED)) {
// The bubble is now gone & the notification is hidden from the shade, so
// time to actually remove it
for (NotifCallback cb : mCallbacks) {
@@ -1111,6 +1086,9 @@
Log.d(TAG, BubbleDebugConfig.formatBubblesString(mStackView.getBubblesOnScreen(),
mStackView.getExpandedBubble()));
}
+ Log.d(TAG, "\n[BubbleData] overflow:");
+ Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getOverflowBubbles(),
+ null) + "\n");
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index f2b1c03..65d5beb 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -74,8 +74,6 @@
@Nullable Bubble selectedBubble;
@Nullable Bubble addedBubble;
@Nullable Bubble updatedBubble;
- @Nullable Bubble addedOverflowBubble;
- @Nullable Bubble removedOverflowBubble;
// Pair with Bubble and @DismissReason Integer
final List<Pair<Bubble, Integer>> removedBubbles = new ArrayList<>();
@@ -94,12 +92,10 @@
|| addedBubble != null
|| updatedBubble != null
|| !removedBubbles.isEmpty()
- || addedOverflowBubble != null
- || removedOverflowBubble != null
|| orderChanged;
}
- void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) {
+ void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) {
removedBubbles.add(new Pair<>(bubbleToRemove, reason));
}
}
@@ -237,7 +233,6 @@
private void moveOverflowBubbleToPending(Bubble b) {
mOverflowBubbles.remove(b);
- mStateChange.removedOverflowBubble = b;
mPendingBubbles.add(b);
}
@@ -445,9 +440,8 @@
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "Cancel overflow bubble: " + b);
}
- mOverflowBubbles.remove(b);
mStateChange.bubbleRemoved(b, reason);
- mStateChange.removedOverflowBubble = b;
+ mOverflowBubbles.remove(b);
}
return;
}
@@ -489,7 +483,6 @@
Log.d(TAG, "Overflowing: " + bubble);
}
mOverflowBubbles.add(0, bubble);
- mStateChange.addedOverflowBubble = bubble;
bubble.stopInflation();
if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
// Remove oldest bubble.
@@ -497,9 +490,8 @@
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "Overflow full. Remove: " + oldest);
}
- mOverflowBubbles.remove(oldest);
- mStateChange.removedOverflowBubble = oldest;
mStateChange.bubbleRemoved(oldest, BubbleController.DISMISS_OVERFLOW_MAX_REACHED);
+ mOverflowBubbles.remove(oldest);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index c2ca9fa..0074249 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -108,12 +108,11 @@
mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight);
mRecyclerView.setAdapter(mAdapter);
-
- mOverflowBubbles.addAll(mBubbleController.getOverflowBubbles());
- mAdapter.notifyDataSetChanged();
- setEmptyStateVisibility();
-
- mBubbleController.setOverflowListener(mDataListener);
+ onDataChanged(mBubbleController.getOverflowBubbles());
+ mBubbleController.setOverflowCallback(() -> {
+ onDataChanged(mBubbleController.getOverflowBubbles());
+ });
+ onThemeChanged();
}
/**
@@ -140,14 +139,6 @@
}
}
- void setEmptyStateVisibility() {
- if (mOverflowBubbles.isEmpty()) {
- mEmptyState.setVisibility(View.VISIBLE);
- } else {
- mEmptyState.setVisibility(View.GONE);
- }
- }
-
void setBackgroundColor() {
final TypedArray ta = getApplicationContext().obtainStyledAttributes(
new int[]{android.R.attr.colorBackgroundFloating});
@@ -156,40 +147,22 @@
findViewById(android.R.id.content).setBackgroundColor(bgColor);
}
- private final BubbleData.Listener mDataListener = new BubbleData.Listener() {
+ void onDataChanged(List<Bubble> bubbles) {
+ mOverflowBubbles.clear();
+ mOverflowBubbles.addAll(bubbles);
+ mAdapter.notifyDataSetChanged();
- @Override
- public void applyUpdate(BubbleData.Update update) {
-
- Bubble toRemove = update.removedOverflowBubble;
- if (toRemove != null) {
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, "remove: " + toRemove);
- }
- toRemove.cleanupViews();
- int i = mOverflowBubbles.indexOf(toRemove);
- mOverflowBubbles.remove(toRemove);
- mAdapter.notifyItemRemoved(i);
- }
-
- Bubble toAdd = update.addedOverflowBubble;
- if (toAdd != null) {
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, "add: " + toAdd);
- }
- mOverflowBubbles.add(0, toAdd);
- mAdapter.notifyItemInserted(0);
- }
-
- setEmptyStateVisibility();
-
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(
- mBubbleController.getOverflowBubbles(),
- null));
- }
+ if (mOverflowBubbles.isEmpty()) {
+ mEmptyState.setVisibility(View.VISIBLE);
+ } else {
+ mEmptyState.setVisibility(View.GONE);
}
- };
+
+ if (DEBUG_OVERFLOW) {
+ Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString(
+ mOverflowBubbles, /*selected*/ null));
+ }
+ }
@Override
public void onStart() {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index e3b630b..d1d07f6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -18,6 +18,7 @@
import android.app.INotificationManager;
import android.content.Context;
+import android.view.WindowManager;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.bubbles.BubbleData;
@@ -68,7 +69,8 @@
FloatingContentCoordinator floatingContentCoordinator,
BubbleDataRepository bubbleDataRepository,
SysUiState sysUiState,
- INotificationManager notifManager) {
+ INotificationManager notifManager,
+ WindowManager windowManager) {
return new BubbleController(
context,
notificationShadeWindowController,
@@ -88,6 +90,7 @@
floatingContentCoordinator,
bubbleDataRepository,
sysUiState,
- notifManager);
+ notifManager,
+ windowManager);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
index 1f07e37..b6c09f1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
@@ -50,7 +50,8 @@
fun createPinDialog(
cvh: ControlViewHolder,
useAlphaNumeric: Boolean,
- useRetryStrings: Boolean
+ useRetryStrings: Boolean,
+ onCancel: () -> Unit
): Dialog? {
val lastAction = cvh.lastAction
if (lastAction == null) {
@@ -86,7 +87,10 @@
})
setNegativeButton(
android.R.string.cancel,
- DialogInterface.OnClickListener { dialog, _ -> dialog.cancel() }
+ DialogInterface.OnClickListener { dialog, _ ->
+ onCancel.invoke()
+ dialog.cancel()
+ }
)
}
return builder.create().apply {
@@ -111,7 +115,7 @@
/**
* AlertDialogs to handle [ControlAction#RESPONSE_CHALLENGE_ACK] response type.
*/
- fun createConfirmationDialog(cvh: ControlViewHolder): Dialog? {
+ fun createConfirmationDialog(cvh: ControlViewHolder, onCancel: () -> Unit): Dialog? {
val lastAction = cvh.lastAction
if (lastAction == null) {
Log.e(ControlsUiController.TAG,
@@ -130,7 +134,10 @@
})
setNegativeButton(
android.R.string.cancel,
- DialogInterface.OnClickListener { dialog, _ -> dialog.cancel() }
+ DialogInterface.OnClickListener { dialog, _ ->
+ onCancel.invoke()
+ dialog.cancel()
+ }
)
}
return builder.create().apply {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 2653ce0..f30756b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -86,6 +86,7 @@
var behavior: Behavior? = null
var lastAction: ControlAction? = null
private var lastChallengeDialog: Dialog? = null
+ private val onDialogCancel: () -> Unit = { lastChallengeDialog = null }
val deviceType: Int
get() = cws.control?.let { it.getDeviceType() } ?: cws.ci.deviceType
@@ -154,15 +155,18 @@
setTransientStatus(context.resources.getString(R.string.controls_error_failed))
}
ControlAction.RESPONSE_CHALLENGE_PIN -> {
- lastChallengeDialog = ChallengeDialogs.createPinDialog(this, false, failedAttempt)
+ lastChallengeDialog = ChallengeDialogs.createPinDialog(
+ this, false, failedAttempt, onDialogCancel)
lastChallengeDialog?.show()
}
ControlAction.RESPONSE_CHALLENGE_PASSPHRASE -> {
- lastChallengeDialog = ChallengeDialogs.createPinDialog(this, true, failedAttempt)
+ lastChallengeDialog = ChallengeDialogs.createPinDialog(
+ this, false, failedAttempt, onDialogCancel)
lastChallengeDialog?.show()
}
ControlAction.RESPONSE_CHALLENGE_ACK -> {
- lastChallengeDialog = ChallengeDialogs.createConfirmationDialog(this)
+ lastChallengeDialog = ChallengeDialogs.createConfirmationDialog(
+ this, onDialogCancel)
lastChallengeDialog?.show()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 96494cf..3a37c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -451,7 +451,8 @@
* @param metadata New metadata.
*/
@Override
- public void onMetadataOrStateChanged(MediaMetadata metadata, @PlaybackState.State int state) {
+ public void onPrimaryMetadataOrStateChanged(MediaMetadata metadata,
+ @PlaybackState.State int state) {
synchronized (this) {
boolean nextVisible = NotificationMediaManager.isPlayingState(state);
mMediaHandler.removeCallbacksAndMessages(null);
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 123cf78..9c89fee 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -61,6 +61,18 @@
return buffer;
}
+ /** Provides a logging buffer for all logs related to managing notification sections. */
+ @Provides
+ @Singleton
+ @NotificationSectionLog
+ public static LogBuffer provideNotificationSectionLogBuffer(
+ LogcatEchoTracker bufferFilter,
+ DumpManager dumpManager) {
+ LogBuffer buffer = new LogBuffer("NotifSectionLog", 500, 10, bufferFilter);
+ buffer.attach(dumpManager);
+ return buffer;
+ }
+
/** Provides a logging buffer for all logs related to the data layer of notifications. */
@Provides
@Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
new file mode 100644
index 0000000..7259eeb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for notification sections-related messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NotificationSectionLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/GoneChildrenHideHelper.kt b/packages/SystemUI/src/com/android/systemui/media/GoneChildrenHideHelper.kt
new file mode 100644
index 0000000..2fe0d9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/GoneChildrenHideHelper.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.media
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+
+private val EMPTY_RECT = Rect(0,0,0,0)
+
+private val LAYOUT_CHANGE_LISTENER = object : View.OnLayoutChangeListener {
+
+ override fun onLayoutChange(v: View?, left: Int, top: Int, right: Int, bottom: Int,
+ oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
+ v?.let {
+ if (v.visibility == View.GONE) {
+ v.clipBounds = EMPTY_RECT
+ } else {
+ v.clipBounds = null
+ }
+ }
+ }
+}
+/**
+ * A helper class that clips all GONE children. Useful for transitions in motionlayout which
+ * don't clip its children.
+ */
+class GoneChildrenHideHelper private constructor() {
+ companion object {
+ @JvmStatic
+ fun clipGoneChildrenOnLayout(layout: ViewGroup) {
+ val childCount = layout.childCount
+ for (i in 0 until childCount) {
+ val child = layout.getChildAt(i)
+ child.addOnLayoutChangeListener(LAYOUT_CHANGE_LISTENER)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt
index 9374727..7432165 100644
--- a/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt
@@ -6,6 +6,7 @@
import android.animation.ValueAnimator
import android.content.res.ColorStateList
import android.content.res.Resources
+import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
@@ -49,6 +50,7 @@
@Keep
class IlluminationDrawable : Drawable() {
+ private var themeAttrs: IntArray? = null
private var cornerRadius = 0f
private var highlightColor = Color.TRANSPARENT
private val rippleData = RippleData(0f, 0f, 0f, 0f, 0f, 0f, 0f)
@@ -139,13 +141,41 @@
theme: Resources.Theme?
) {
val a = obtainAttributes(r, theme, attrs, R.styleable.IlluminationDrawable)
- cornerRadius = a.getDimension(R.styleable.IlluminationDrawable_cornerRadius, cornerRadius)
- rippleData.minSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMinSize, 0f)
- rippleData.maxSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMaxSize, 0f)
- rippleData.highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f
+ themeAttrs = a.extractThemeAttrs()
+ updateStateFromTypedArray(a)
a.recycle()
}
+ private fun updateStateFromTypedArray(a: TypedArray) {
+ if (a.hasValue(R.styleable.IlluminationDrawable_cornerRadius)) {
+ cornerRadius = a.getDimension(R.styleable.IlluminationDrawable_cornerRadius,
+ cornerRadius)
+ }
+ if (a.hasValue(R.styleable.IlluminationDrawable_rippleMinSize)) {
+ rippleData.minSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMinSize, 0f)
+ }
+ if (a.hasValue(R.styleable.IlluminationDrawable_rippleMaxSize)) {
+ rippleData.maxSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMaxSize, 0f)
+ }
+ if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) {
+ rippleData.highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) /
+ 100f
+ }
+ }
+
+ override fun canApplyTheme(): Boolean {
+ return themeAttrs != null && themeAttrs!!.size > 0 || super.canApplyTheme()
+ }
+
+ override fun applyTheme(t: Resources.Theme) {
+ super.applyTheme(t)
+ themeAttrs?.let {
+ val a = t.resolveAttributes(it, R.styleable.IlluminationDrawable)
+ updateStateFromTypedArray(a)
+ a.recycle()
+ }
+ }
+
override fun setColorFilter(p0: ColorFilter?) {
throw UnsupportedOperationException("Color filters are not supported")
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
new file mode 100644
index 0000000..524c695
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.view.View
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.stack.MediaHeaderView
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * A class that controls the media notifications on the lock screen, handles its visibility and
+ * is responsible for the embedding of he media experience.
+ */
+@Singleton
+class KeyguardMediaController @Inject constructor(
+ private val mediaHost: MediaHost,
+ private val bypassController: KeyguardBypassController,
+ private val statusBarStateController: SysuiStatusBarStateController
+) {
+
+ init {
+ statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ updateVisibility()
+ }
+ })
+ }
+ private var view: MediaHeaderView? = null
+
+ /**
+ * Attach this controller to a media view, initializing its state
+ */
+ fun attach(mediaView: MediaHeaderView) {
+ view = mediaView
+ // First let's set the desired state that we want for this host
+ mediaHost.visibleChangedListener = { updateVisibility() }
+ mediaHost.expansion = 0.0f
+ mediaHost.showsOnlyActiveMedia = true
+
+ // Let's now initialize this view, which also creates the host view for us.
+ mediaHost.init(MediaHierarchyManager.LOCATION_LOCKSCREEN)
+ mediaView.setContentView(mediaHost.hostView)
+ }
+
+ private fun updateVisibility() {
+ val shouldBeVisible = mediaHost.visible
+ && !bypassController.bypassEnabled
+ && (statusBarStateController.state == StatusBarState.KEYGUARD ||
+ statusBarStateController.state == StatusBarState.FULLSCREEN_USER_SWITCHER)
+ view?.visibility = if (shouldBeVisible) View.VISIBLE else View.GONE
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/media/LayoutAnimationHelper.kt b/packages/SystemUI/src/com/android/systemui/media/LayoutAnimationHelper.kt
new file mode 100644
index 0000000..a366725
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/LayoutAnimationHelper.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.media
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import com.android.systemui.statusbar.notification.AnimatableProperty
+import com.android.systemui.statusbar.notification.PropertyAnimator
+import com.android.systemui.statusbar.notification.stack.AnimationProperties
+
+/**
+ * A utility class that helps with animations of bound changes designed for motionlayout which
+ * doesn't work together with regular changeBounds.
+ */
+class LayoutAnimationHelper {
+
+ private val layout: ViewGroup
+ private var sizeAnimationPending = false
+ private val desiredBounds = mutableMapOf<View, Rect>()
+ private val animationProperties = AnimationProperties()
+ private val layoutListener = object : View.OnLayoutChangeListener {
+ override fun onLayoutChange(v: View?, left: Int, top: Int, right: Int, bottom: Int,
+ oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
+ v?.let {
+ if (v.alpha == 0.0f || v.visibility == View.GONE || oldLeft - oldRight == 0 ||
+ oldTop - oldBottom == 0) {
+ return
+ }
+ if (oldLeft != left || oldTop != top || oldBottom != bottom || oldRight != right) {
+ val rect = desiredBounds.getOrPut(v, { Rect() })
+ rect.set(left, top, right, bottom)
+ onDesiredLocationChanged(v, rect)
+ }
+ }
+ }
+ }
+
+ constructor(layout: ViewGroup) {
+ this.layout = layout
+ val childCount = this.layout.childCount
+ for (i in 0 until childCount) {
+ val child = this.layout.getChildAt(i)
+ child.addOnLayoutChangeListener(layoutListener)
+ }
+ }
+
+ private fun onDesiredLocationChanged(v: View, rect: Rect) {
+ if (!sizeAnimationPending) {
+ applyBounds(v, rect, animate = false)
+ }
+ // We need to reapply the current bounds in every frame since the layout may override
+ // the layout bounds making this view jump and not all calls to apply bounds actually
+ // reapply them, for example if there's already an animator to the same target
+ reapplyProperty(v, AnimatableProperty.ABSOLUTE_X);
+ reapplyProperty(v, AnimatableProperty.ABSOLUTE_Y);
+ reapplyProperty(v, AnimatableProperty.WIDTH);
+ reapplyProperty(v, AnimatableProperty.HEIGHT);
+ }
+
+ private fun reapplyProperty(v: View, property: AnimatableProperty) {
+ property.property.set(v, property.property.get(v))
+ }
+
+ private fun applyBounds(v: View, newBounds: Rect, animate: Boolean) {
+ PropertyAnimator.setProperty(v, AnimatableProperty.ABSOLUTE_X, newBounds.left.toFloat(),
+ animationProperties, animate)
+ PropertyAnimator.setProperty(v, AnimatableProperty.ABSOLUTE_Y, newBounds.top.toFloat(),
+ animationProperties, animate)
+ PropertyAnimator.setProperty(v, AnimatableProperty.WIDTH, newBounds.width().toFloat(),
+ animationProperties, animate)
+ PropertyAnimator.setProperty(v, AnimatableProperty.HEIGHT, newBounds.height().toFloat(),
+ animationProperties, animate)
+ }
+
+ private fun startBoundAnimation(v: View) {
+ val target = desiredBounds[v] ?: return
+ applyBounds(v, target, animate = true)
+ }
+
+ fun animatePendingSizeChange(duration: Long, delay: Long) {
+ animationProperties.duration = duration
+ animationProperties.delay = delay
+ if (!sizeAnimationPending) {
+ sizeAnimationPending = true
+ layout.viewTreeObserver.addOnPreDrawListener (
+ object : ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ layout.viewTreeObserver.removeOnPreDrawListener(this)
+ sizeAnimationPending = false
+ val childCount = layout.childCount
+ for (i in 0 until childCount) {
+ val child = layout.getChildAt(i)
+ startBoundAnimation(child)
+ }
+ return true
+ }
+ })
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 557132b..c3a7d9f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -16,10 +16,8 @@
package com.android.systemui.media;
-import android.annotation.LayoutRes;
import android.app.PendingIntent;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -27,35 +25,36 @@
import android.content.pm.ResolveInfo;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
-import android.graphics.ImageDecoder;
+import android.graphics.Canvas;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.RippleDrawable;
-import android.media.MediaDescription;
-import android.media.MediaMetadata;
-import android.media.ThumbnailUtils;
import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
-import android.net.Uri;
import android.service.media.MediaBrowserService;
-import android.text.TextUtils;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnAttachStateChangeListener;
-import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.constraintlayout.motion.widget.Key;
+import androidx.constraintlayout.motion.widget.KeyAttributes;
+import androidx.constraintlayout.motion.widget.KeyFrames;
+import androidx.constraintlayout.motion.widget.MotionLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+import com.android.settingslib.Utils;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputSliceConstants;
@@ -64,24 +63,39 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSMediaBrowser;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.concurrency.DelayableExecutor;
-import java.io.IOException;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
- * Base media control panel for System UI
+ * A view controller used for Media Playback.
*/
public class MediaControlPanel {
private static final String TAG = "MediaControlPanel";
@Nullable private final LocalMediaManager mLocalMediaManager;
+
+ // Button IDs for QS controls
+ static final int[] ACTION_IDS = {
+ R.id.action0,
+ R.id.action1,
+ R.id.action2,
+ R.id.action3,
+ R.id.action4
+ };
+
+ private final SeekBarViewModel mSeekBarViewModel;
+ private SeekBarObserver mSeekBarObserver;
private final Executor mForegroundExecutor;
protected final Executor mBackgroundExecutor;
private final ActivityStarter mActivityStarter;
+ private LayoutAnimationHelper mLayoutAnimationHelper;
private Context mContext;
- protected LinearLayout mMediaNotifView;
- private View mSeamless;
+ private PlayerViewHolder mViewHolder;
private MediaSession.Token mToken;
private MediaController mController;
private int mForegroundColor;
@@ -89,9 +103,11 @@
private MediaDevice mDevice;
protected ComponentName mServiceComponent;
private boolean mIsRegistered = false;
+ private List<KeyFrames> mKeyFrames;
private String mKey;
-
- private final int[] mActionIds;
+ private int mAlbumArtSize;
+ private int mAlbumArtRadius;
+ private int mViewWidth;
public static final String MEDIA_PREFERENCES = "media_control_prefs";
public static final String MEDIA_PREFERENCE_KEY = "browser_components";
@@ -100,22 +116,6 @@
private boolean mIsRemotePlayback;
private QSMediaBrowser mQSMediaBrowser;
- // Button IDs used in notifications
- protected static final int[] NOTIF_ACTION_IDS = {
- com.android.internal.R.id.action0,
- com.android.internal.R.id.action1,
- com.android.internal.R.id.action2,
- com.android.internal.R.id.action3,
- com.android.internal.R.id.action4
- };
-
- // URI fields to try loading album art from
- private static final String[] ART_URIS = {
- MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
- MediaMetadata.METADATA_KEY_ART_URI,
- MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
- };
-
private final MediaController.Callback mSessionCallback = new MediaController.Callback() {
@Override
public void onSessionDestroyed() {
@@ -135,17 +135,6 @@
}
};
- private final OnAttachStateChangeListener mStateListener = new OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View unused) {
- makeActive();
- }
- @Override
- public void onViewDetachedFromWindow(View unused) {
- makeInactive();
- }
- };
-
private final LocalMediaManager.DeviceCallback mDeviceCallback =
new LocalMediaManager.DeviceCallback() {
@Override
@@ -173,40 +162,55 @@
/**
* Initialize a new control panel
* @param context
- * @param parent
* @param routeManager Manager used to listen for device change events.
- * @param layoutId layout resource to use for this control panel
- * @param actionIds resource IDs for action buttons in the layout
* @param foregroundExecutor foreground executor
* @param backgroundExecutor background executor, used for processing artwork
* @param activityStarter activity starter
*/
- public MediaControlPanel(Context context, ViewGroup parent,
- @Nullable LocalMediaManager routeManager, @LayoutRes int layoutId, int[] actionIds,
- Executor foregroundExecutor, Executor backgroundExecutor,
+ public MediaControlPanel(Context context, @Nullable LocalMediaManager routeManager,
+ Executor foregroundExecutor, DelayableExecutor backgroundExecutor,
ActivityStarter activityStarter) {
mContext = context;
- LayoutInflater inflater = LayoutInflater.from(mContext);
- mMediaNotifView = (LinearLayout) inflater.inflate(layoutId, parent, false);
- // TODO(b/150854549): removeOnAttachStateChangeListener when this doesn't inflate views
- // mStateListener shouldn't need to be unregistered since this object shares the same
- // lifecycle with the inflated view. It would be better, however, if this controller used an
- // attach/detach of views instead of inflating them in the constructor, which would allow
- // mStateListener to be unregistered in detach.
- mMediaNotifView.addOnAttachStateChangeListener(mStateListener);
mLocalMediaManager = routeManager;
- mActionIds = actionIds;
mForegroundExecutor = foregroundExecutor;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
+ mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
+ loadDimens();
+ }
+
+ public void onDestroy() {
+ if (mSeekBarObserver != null) {
+ mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver);
+ }
+ makeInactive();
+ }
+
+ private void loadDimens() {
+ mAlbumArtRadius = mContext.getResources().getDimensionPixelSize(
+ Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+ mAlbumArtSize = mContext.getResources().getDimensionPixelSize(R.dimen.qs_media_album_size);
}
/**
- * Get the view used to display media controls
- * @return the view
+ * Get the view holder used to display media controls
+ * @return the view holder
*/
- public View getView() {
- return mMediaNotifView;
+ @Nullable
+ public PlayerViewHolder getView() {
+ return mViewHolder;
+ }
+
+ /**
+ * Sets the listening state of the player.
+ *
+ * Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid
+ * unnecessary work when the QS panel is closed.
+ *
+ * @param listening True when player should be active. Otherwise, false.
+ */
+ public void setListening(boolean listening) {
+ mSeekBarViewModel.setListening(listening);
}
/**
@@ -217,21 +221,30 @@
return mContext;
}
+ /** Attaches the player to the view holder. */
+ public void attach(PlayerViewHolder vh) {
+ mViewHolder = vh;
+ MotionLayout motionView = vh.getPlayer();
+ mLayoutAnimationHelper = new LayoutAnimationHelper(motionView);
+ GoneChildrenHideHelper.clipGoneChildrenOnLayout(motionView);
+ mKeyFrames = motionView.getDefinedTransitions().get(0).getKeyFrameList();
+ mSeekBarObserver = new SeekBarObserver(motionView);
+ mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver);
+ SeekBar bar = vh.getSeekBar();
+ bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener());
+ bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener());
+ }
+
/**
- * Update the media panel view for the given media session
- * @param token
- * @param iconDrawable
- * @param largeIcon
- * @param iconColor
- * @param bgColor
- * @param contentIntent
- * @param appNameString
- * @param key
+ * Bind this view based on the data given
*/
- public void setMediaSession(MediaSession.Token token, Drawable iconDrawable, Icon largeIcon,
- int iconColor, int bgColor, PendingIntent contentIntent, String appNameString,
- String key) {
- // Ensure that component names are updated if token has changed
+ public void bind(@NotNull MediaData data) {
+ if (mViewHolder == null) {
+ return;
+ }
+ MediaSession.Token token = data.getToken();
+ mForegroundColor = data.getForegroundColor();
+ mBackgroundColor = data.getBackgroundColor();
if (mToken == null || !mToken.equals(token)) {
if (mQSMediaBrowser != null) {
Log.d(TAG, "Disconnecting old media browser");
@@ -243,20 +256,21 @@
mCheckedForResumption = false;
}
- mForegroundColor = iconColor;
- mBackgroundColor = bgColor;
mController = new MediaController(mContext, mToken);
- mKey = key;
+
+ ConstraintSet expandedSet = mViewHolder.getPlayer().getConstraintSet(R.id.expanded);
+ ConstraintSet collapsedSet = mViewHolder.getPlayer().getConstraintSet(R.id.collapsed);
// Try to find a browser service component for this app
// TODO also check for a media button receiver intended for restarting (b/154127084)
// Only check if we haven't tried yet or the session token changed
- final String pkgName = mController.getPackageName();
+ final String pkgName = data.getPackageName();
if (mServiceComponent == null && !mCheckedForResumption) {
Log.d(TAG, "Checking for service component");
PackageManager pm = mContext.getPackageManager();
Intent resumeIntent = new Intent(MediaBrowserService.SERVICE_INTERFACE);
List<ResolveInfo> resumeInfo = pm.queryIntentServices(resumeIntent, 0);
+ // TODO: look into this resumption
if (resumeInfo != null) {
for (ResolveInfo inf : resumeInfo) {
if (inf.serviceInfo.packageName.equals(mController.getPackageName())) {
@@ -271,38 +285,61 @@
mController.registerCallback(mSessionCallback);
- mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
+ mViewHolder.getBackground().setBackgroundTintList(
+ ColorStateList.valueOf(mBackgroundColor));
// Click action
- if (contentIntent != null) {
- mMediaNotifView.setOnClickListener(v -> {
- mActivityStarter.postStartActivityDismissingKeyguard(contentIntent);
+ PendingIntent clickIntent = data.getClickIntent();
+ if (clickIntent != null) {
+ mViewHolder.getPlayer().setOnClickListener(v -> {
+ mActivityStarter.postStartActivityDismissingKeyguard(clickIntent);
});
}
+ ImageView albumView = mViewHolder.getAlbumView();
+ // TODO: migrate this to a view with rounded corners instead of baking the rounding
+ // into the bitmap
+ Drawable artwork = createRoundedBitmap(data.getArtwork());
+ albumView.setImageDrawable(artwork);
+
// App icon
- ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
+ ImageView appIcon = mViewHolder.getAppIcon();
+ Drawable iconDrawable = data.getAppIcon().mutate();
iconDrawable.setTint(mForegroundColor);
appIcon.setImageDrawable(iconDrawable);
+ // Song name
+ TextView titleText = mViewHolder.getTitleText();
+ titleText.setText(data.getSong());
+ titleText.setTextColor(data.getForegroundColor());
+
+ // App title
+ TextView appName = mViewHolder.getAppName();
+ appName.setText(data.getApp());
+ appName.setTextColor(mForegroundColor);
+
+ // Artist name
+ TextView artistText = mViewHolder.getArtistText();
+ artistText.setText(data.getArtist());
+ artistText.setTextColor(mForegroundColor);
+
// Transfer chip
- mSeamless = mMediaNotifView.findViewById(R.id.media_seamless);
- if (mSeamless != null) {
- if (mLocalMediaManager != null) {
- mSeamless.setVisibility(View.VISIBLE);
- updateDevice(mLocalMediaManager.getCurrentConnectedDevice());
- mSeamless.setOnClickListener(v -> {
- final Intent intent = new Intent()
- .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
- .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
- mController.getPackageName())
- .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
- mActivityStarter.startActivity(intent, false, true /* dismissShade */,
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- });
- } else {
- Log.d(TAG, "LocalMediaManager is null. Not binding output chip for pkg=" + pkgName);
- }
+ if (mLocalMediaManager != null) {
+ mViewHolder.getSeamless().setVisibility(View.VISIBLE);
+ setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
+ setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
+ updateDevice(mLocalMediaManager.getCurrentConnectedDevice());
+ mViewHolder.getSeamless().setOnClickListener(v -> {
+ final Intent intent = new Intent()
+ .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
+ .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
+ mController.getPackageName())
+ .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
+ mActivityStarter.startActivity(intent, false, true /* dismissShade */,
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ });
+ } else {
+ Log.d(TAG, "LocalMediaManager is null. Not binding output chip for pkg=" + pkgName);
}
PlaybackInfo playbackInfo = mController.getPlaybackInfo();
if (playbackInfo != null) {
@@ -311,43 +348,113 @@
Log.d(TAG, "PlaybackInfo was null. Defaulting to local playback.");
mIsRemotePlayback = false;
}
+ List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
+ // Media controls
+ int i = 0;
+ List<MediaAction> actionIcons = data.getActions();
+ for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) {
+ int actionId = ACTION_IDS[i];
+ final ImageButton button = mViewHolder.getAction(actionId);
+ MediaAction mediaAction = actionIcons.get(i);
+ button.setImageDrawable(mediaAction.getDrawable());
+ button.setContentDescription(mediaAction.getContentDescription());
+ button.setImageTintList(ColorStateList.valueOf(mForegroundColor));
+ PendingIntent actionIntent = mediaAction.getIntent();
+
+ if (mViewHolder.getBackground().getBackground() instanceof IlluminationDrawable) {
+ ((IlluminationDrawable) mViewHolder.getBackground().getBackground())
+ .setupTouch(button, mViewHolder.getPlayer());
+ }
+
+ button.setOnClickListener(v -> {
+ if (actionIntent != null) {
+ try {
+ actionIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ boolean visibleInCompat = actionsWhenCollapsed.contains(i);
+ updateKeyFrameVisibility(actionId, visibleInCompat);
+ setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
+ setVisibleAndAlpha(expandedSet, actionId, true /*visible */);
+ }
+
+ // Hide any unused buttons
+ for (; i < ACTION_IDS.length; i++) {
+ setVisibleAndAlpha(expandedSet, ACTION_IDS[i], false /*visible */);
+ setVisibleAndAlpha(collapsedSet, ACTION_IDS[i], false /*visible */);
+ }
+
+ // Seek Bar
+ final MediaController controller = getController();
+ mBackgroundExecutor.execute(
+ () -> mSeekBarViewModel.updateController(controller, data.getForegroundColor()));
+
+ // Set up long press menu
+ // TODO: b/156036025 bring back media guts
makeActive();
- // App title (not in mini player)
- TextView appName = mMediaNotifView.findViewById(R.id.app_name);
- if (appName != null) {
- appName.setText(appNameString);
- appName.setTextColor(mForegroundColor);
+ // Update both constraint sets to regenerate the animation.
+ mViewHolder.getPlayer().updateState(R.id.collapsed, collapsedSet);
+ mViewHolder.getPlayer().updateState(R.id.expanded, expandedSet);
+ }
+
+ @UiThread
+ private Drawable createRoundedBitmap(Icon icon) {
+ if (icon == null) {
+ return null;
}
-
- // Can be null!
- MediaMetadata mediaMetadata = mController.getMetadata();
-
- ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
- if (albumView != null) {
- // Resize art in a background thread
- mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, largeIcon, albumView));
+ // Let's scale down the View, such that the content always nicely fills the view.
+ // ThumbnailUtils actually scales it down such that it may not be filled for odd aspect
+ // ratios
+ Drawable drawable = icon.loadDrawable(mContext);
+ float aspectRatio = drawable.getIntrinsicHeight() / (float) drawable.getIntrinsicWidth();
+ Rect bounds;
+ if (aspectRatio > 1.0f) {
+ bounds = new Rect(0, 0, mAlbumArtSize, (int) (mAlbumArtSize * aspectRatio));
+ } else {
+ bounds = new Rect(0, 0, (int) (mAlbumArtSize / aspectRatio), mAlbumArtSize);
}
-
- // Song name
- TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
- String songName = "";
- if (mediaMetadata != null) {
- songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+ if (bounds.width() > mAlbumArtSize || bounds.height() > mAlbumArtSize) {
+ float offsetX = (bounds.width() - mAlbumArtSize) / 2.0f;
+ float offsetY = (bounds.height() - mAlbumArtSize) / 2.0f;
+ bounds.offset((int) -offsetX,(int) -offsetY);
}
- titleText.setText(songName);
- titleText.setTextColor(mForegroundColor);
+ drawable.setBounds(bounds);
+ Bitmap scaled = Bitmap.createBitmap(mAlbumArtSize, mAlbumArtSize,
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(scaled);
+ drawable.draw(canvas);
+ RoundedBitmapDrawable artwork = RoundedBitmapDrawableFactory.create(
+ mContext.getResources(), scaled);
+ artwork.setCornerRadius(mAlbumArtRadius);
+ return artwork;
+ }
- // Artist name (not in mini player)
- TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
- if (artistText != null) {
- String artistName = "";
- if (mediaMetadata != null) {
- artistName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+ /**
+ * Updates the keyframe visibility such that only views that are not visible actually go
+ * through a transition and fade in.
+ *
+ * @param actionId the id to change
+ * @param visible is the view visible
+ */
+ private void updateKeyFrameVisibility(int actionId, boolean visible) {
+ if (mKeyFrames == null) {
+ return;
+ }
+ for (int i = 0; i < mKeyFrames.size(); i++) {
+ KeyFrames keyframe = mKeyFrames.get(i);
+ ArrayList<Key> viewKeyFrames = keyframe.getKeyFramesForView(actionId);
+ for (int j = 0; j < viewKeyFrames.size(); j++) {
+ Key key = viewKeyFrames.get(j);
+ if (key instanceof KeyAttributes) {
+ KeyAttributes attributes = (KeyAttributes) key;
+ attributes.setValue("alpha", visible ? 1.0f : 0.0f);
+ }
}
- artistText.setText(artistName);
- artistText.setTextColor(mForegroundColor);
}
}
@@ -421,156 +528,42 @@
}
/**
- * Process album art for layout
- * @param description media description
- * @param albumView view to hold the album art
- */
- protected void processAlbumArt(MediaDescription description, ImageView albumView) {
- Bitmap albumArt = null;
-
- // First try loading from URI
- albumArt = loadBitmapFromUri(description.getIconUri());
-
- // Then check bitmap
- if (albumArt == null) {
- albumArt = description.getIconBitmap();
- }
-
- processAlbumArtInternal(albumArt, albumView);
- }
-
- /**
- * Process album art for layout
- * @param metadata media metadata
- * @param largeIcon from notification, checked as a fallback if metadata does not have art
- * @param albumView view to hold the album art
- */
- private void processAlbumArt(MediaMetadata metadata, Icon largeIcon, ImageView albumView) {
- Bitmap albumArt = null;
-
- if (metadata != null) {
- // First look in URI fields
- for (String field : ART_URIS) {
- String uriString = metadata.getString(field);
- if (!TextUtils.isEmpty(uriString)) {
- albumArt = loadBitmapFromUri(Uri.parse(uriString));
- if (albumArt != null) {
- Log.d(TAG, "loaded art from " + field);
- break;
- }
- }
- }
-
- // Then check bitmap field
- if (albumArt == null) {
- albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
- }
- }
-
- // Finally try the notification's largeIcon
- if (albumArt == null && largeIcon != null) {
- albumArt = largeIcon.getBitmap();
- }
-
- processAlbumArtInternal(albumArt, albumView);
- }
-
- /**
- * Load a bitmap from a URI
- * @param uri
- * @return bitmap, or null if couldn't be loaded
- */
- private Bitmap loadBitmapFromUri(Uri uri) {
- // ImageDecoder requires a scheme of the following types
- if (uri.getScheme() == null) {
- return null;
- }
-
- if (!uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)
- && !uri.getScheme().equals(ContentResolver.SCHEME_ANDROID_RESOURCE)
- && !uri.getScheme().equals(ContentResolver.SCHEME_FILE)) {
- return null;
- }
-
- ImageDecoder.Source source = ImageDecoder.createSource(mContext.getContentResolver(), uri);
- try {
- return ImageDecoder.decodeBitmap(source);
- } catch (IOException e) {
- e.printStackTrace();
- return null;
- }
- }
-
- /**
- * Resize and crop the image if provided and update the control view
- * @param albumArt Bitmap of art to display, or null to hide view
- * @param albumView View that will hold the art
- */
- private void processAlbumArtInternal(@Nullable Bitmap albumArt, ImageView albumView) {
- // Resize
- RoundedBitmapDrawable roundedDrawable = null;
- if (albumArt != null) {
- float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
- Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
- int albumSize = (int) mContext.getResources().getDimension(
- R.dimen.qs_media_album_size);
- Bitmap scaled = ThumbnailUtils.extractThumbnail(original, albumSize, albumSize);
- roundedDrawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled);
- roundedDrawable.setCornerRadius(radius);
- } else {
- Log.e(TAG, "No album art available");
- }
-
- // Now that it's resized, update the UI
- final RoundedBitmapDrawable result = roundedDrawable;
- mForegroundExecutor.execute(() -> {
- if (result != null) {
- albumView.setImageDrawable(result);
- albumView.setVisibility(View.VISIBLE);
- } else {
- albumView.setImageDrawable(null);
- albumView.setVisibility(View.GONE);
- }
- });
- }
-
- /**
* Update the current device information
* @param device device information to display
*/
private void updateDevice(MediaDevice device) {
- if (mSeamless == null) {
- return;
- }
mForegroundExecutor.execute(() -> {
updateChipInternal(device);
});
}
private void updateChipInternal(MediaDevice device) {
+ if (mViewHolder == null) {
+ return;
+ }
ColorStateList fgTintList = ColorStateList.valueOf(mForegroundColor);
// Update the outline color
- LinearLayout viewLayout = (LinearLayout) mSeamless;
+ LinearLayout viewLayout = (LinearLayout) mViewHolder.getSeamless();
RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
rect.setStroke(2, mForegroundColor);
rect.setColor(mBackgroundColor);
- ImageView iconView = mSeamless.findViewById(R.id.media_seamless_image);
- TextView deviceName = mSeamless.findViewById(R.id.media_seamless_text);
+ ImageView iconView = mViewHolder.getSeamlessIcon();
+ TextView deviceName = mViewHolder.getSeamlessText();
deviceName.setTextColor(fgTintList);
if (mIsRemotePlayback) {
- mSeamless.setEnabled(false);
- mSeamless.setAlpha(0.38f);
+ mViewHolder.getSeamless().setEnabled(false);
+ mViewHolder.getSeamless().setAlpha(0.38f);
iconView.setImageResource(R.drawable.ic_hardware_speaker);
iconView.setVisibility(View.VISIBLE);
iconView.setImageTintList(fgTintList);
deviceName.setText(R.string.media_seamless_remote_device);
} else if (device != null) {
- mSeamless.setEnabled(true);
- mSeamless.setAlpha(1f);
+ mViewHolder.getSeamless().setEnabled(true);
+ mViewHolder.getSeamless().setAlpha(1f);
Drawable icon = device.getIcon();
iconView.setVisibility(View.VISIBLE);
iconView.setImageTintList(fgTintList);
@@ -586,8 +579,8 @@
} else {
// Reset to default
Log.d(TAG, "device is null. Not binding output chip.");
- mSeamless.setEnabled(true);
- mSeamless.setAlpha(1f);
+ mViewHolder.getSeamless().setEnabled(true);
+ mViewHolder.getSeamless().setAlpha(1f);
iconView.setVisibility(View.GONE);
deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
}
@@ -612,16 +605,20 @@
* Hide the media buttons and show only a restart button
*/
protected void resetButtons() {
+ if (mViewHolder == null) {
+ return;
+ }
// Hide all the old buttons
- for (int i = 0; i < mActionIds.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(mActionIds[i]);
- if (thisBtn != null) {
- thisBtn.setVisibility(View.GONE);
- }
+
+ ConstraintSet expandedSet = mViewHolder.getPlayer().getConstraintSet(R.id.expanded);
+ ConstraintSet collapsedSet = mViewHolder.getPlayer().getConstraintSet(R.id.collapsed);
+ for (int i = 1; i < ACTION_IDS.length; i++) {
+ setVisibleAndAlpha(expandedSet, ACTION_IDS[i], false /*visible */);
+ setVisibleAndAlpha(collapsedSet, ACTION_IDS[i], false /*visible */);
}
// Add a restart button
- ImageButton btn = mMediaNotifView.findViewById(mActionIds[0]);
+ ImageButton btn = mViewHolder.getAction0();
btn.setOnClickListener(v -> {
Log.d(TAG, "Attempting to restart session");
if (mQSMediaBrowser != null) {
@@ -643,7 +640,25 @@
});
btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
- btn.setVisibility(View.VISIBLE);
+ setVisibleAndAlpha(expandedSet, ACTION_IDS[0], true /*visible */);
+ setVisibleAndAlpha(collapsedSet, ACTION_IDS[0], true /*visible */);
+
+ mSeekBarViewModel.clearController();
+ // TODO: fix guts
+ // View guts = mMediaNotifView.findViewById(R.id.media_guts);
+ View options = mViewHolder.getOptions();
+
+ mViewHolder.getPlayer().setOnLongClickListener(v -> {
+ // Replace player view with close/cancel view
+// guts.setVisibility(View.GONE);
+ options.setVisibility(View.VISIBLE);
+ return true; // consumed click
+ });
+ }
+
+ private void setVisibleAndAlpha(ConstraintSet set, int actionId, boolean visible) {
+ set.setVisibility(actionId, visible? ConstraintSet.VISIBLE : ConstraintSet.GONE);
+ set.setAlpha(actionId, visible ? 1.0f : 0.0f);
}
private void makeActive() {
@@ -667,7 +682,6 @@
mIsRegistered = false;
}
}
-
/**
* Verify that we can connect to the given component with a MediaBrowser, and if so, add that
* component to the list of resumption components
@@ -739,4 +753,33 @@
* Called when a player can't be resumed to give it an opportunity to hide or remove itself
*/
protected void removePlayer() { }
+
+ public void measure(@Nullable MediaMeasurementInput input) {
+ if (mViewHolder == null) {
+ return;
+ }
+ if (input != null) {
+ int width = input.getWidth();
+ setPlayerWidth(width);
+ mViewHolder.getPlayer().measure(input.getWidthMeasureSpec(),
+ input.getHeightMeasureSpec());
+ }
+ }
+
+ public void setPlayerWidth(int width) {
+ if (mViewHolder == null) {
+ return;
+ }
+ MotionLayout view = mViewHolder.getPlayer();
+ ConstraintSet expandedSet = view.getConstraintSet(R.id.expanded);
+ ConstraintSet collapsedSet = view.getConstraintSet(R.id.collapsed);
+ collapsedSet.setGuidelineBegin(R.id.view_width, width);
+ expandedSet.setGuidelineBegin(R.id.view_width, width);
+ view.updateState(R.id.collapsed, collapsedSet);
+ view.updateState(R.id.expanded, expandedSet);
+ }
+
+ public void animatePendingSizeChange(long duration, long startDelay) {
+ mLayoutAnimationHelper.animatePendingSizeChange(duration, startDelay);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
new file mode 100644
index 0000000..6a26461
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.media
+
+import android.app.PendingIntent
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.media.session.MediaSession
+
+/** State of a media view. */
+data class MediaData(
+ val initialized: Boolean = false,
+ val foregroundColor: Int,
+ val backgroundColor: Int,
+ val app: String?,
+ val appIcon: Drawable?,
+ val artist: CharSequence?,
+ val song: CharSequence?,
+ val artwork: Icon?,
+ val actions: List<MediaAction>,
+ val actionsToShowInCompact: List<Int>,
+ val packageName: String?,
+ val token: MediaSession.Token?,
+ val clickIntent: PendingIntent?
+)
+
+/** State of a media action. */
+data class MediaAction(
+ val drawable: Drawable?,
+ val intent: PendingIntent?,
+ val contentDescription: CharSequence?
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
new file mode 100644
index 0000000..8da864c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.app.Notification
+import android.content.ContentResolver
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.ImageDecoder
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.media.MediaMetadata
+import android.media.session.MediaSession
+import android.net.Uri
+import android.service.notification.StatusBarNotification
+import android.text.TextUtils
+import android.util.Log
+import com.android.internal.util.ContrastColorUtil
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.notification.MediaNotificationProcessor
+import com.android.systemui.statusbar.notification.row.HybridGroupManager
+import com.android.systemui.util.Utils
+import java.io.IOException
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlin.collections.LinkedHashMap
+
+// URI fields to try loading album art from
+private val ART_URIS = arrayOf(
+ MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+ MediaMetadata.METADATA_KEY_ART_URI,
+ MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+)
+
+private const val TAG = "MediaDataManager"
+
+private val LOADING = MediaData(false, 0, 0, null, null, null, null, null,
+ emptyList(), emptyList(), null, null, null)
+
+/**
+ * A class that facilitates management and loading of Media Data, ready for binding.
+ */
+@Singleton
+class MediaDataManager @Inject constructor(
+ private val context: Context,
+ private val mediaControllerFactory: MediaControllerFactory,
+ @Background private val backgroundExecutor: Executor,
+ @Main private val foregroundExcecutor: Executor
+) {
+
+ private val listeners: MutableSet<Listener> = mutableSetOf()
+ private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+
+ fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
+ if (isMediaNotification(sbn)) {
+ if (!mediaEntries.containsKey(key)) {
+ mediaEntries.put(key, LOADING)
+ }
+ loadMediaData(key, sbn)
+ } else {
+ onNotificationRemoved(key)
+ }
+ }
+
+ private fun loadMediaData(key: String, sbn: StatusBarNotification) {
+ backgroundExecutor.execute {
+ loadMediaDataInBg(key, sbn)
+ }
+ }
+
+ /**
+ * Add a listener for changes in this class
+ */
+ fun addListener(listener: Listener) = listeners.add(listener)
+
+ /**
+ * Remove a listener for changes in this class
+ */
+ fun removeListener(listener: Listener) = listeners.remove(listener)
+
+ private fun loadMediaDataInBg(key: String, sbn: StatusBarNotification) {
+ val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
+ as MediaSession.Token?
+ val metadata = mediaControllerFactory.create(token).metadata
+
+ if (metadata == null) {
+ // TODO: handle this better, removing media notification
+ return
+ }
+
+ // Foreground and Background colors computed from album art
+ val notif: Notification = sbn.notification
+ var fgColor = notif.color
+ var bgColor = -1
+ var artworkBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART)
+ if (artworkBitmap == null) {
+ artworkBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
+ }
+ if (artworkBitmap == null) {
+ artworkBitmap = loadBitmapFromUri(metadata)
+ }
+ val artWorkIcon = if (artworkBitmap == null) {
+ notif.getLargeIcon()
+ } else {
+ Icon.createWithBitmap(artworkBitmap)
+ }
+ if (artWorkIcon != null) {
+ // If we have art, get colors from that
+ if (artworkBitmap == null) {
+ if (artWorkIcon.type == Icon.TYPE_BITMAP ||
+ artWorkIcon.type == Icon.TYPE_ADAPTIVE_BITMAP) {
+ artworkBitmap = artWorkIcon.bitmap
+ } else {
+ val drawable: Drawable = artWorkIcon.loadDrawable(context)
+ artworkBitmap = Bitmap.createBitmap(
+ drawable.intrinsicWidth,
+ drawable.intrinsicHeight,
+ Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(artworkBitmap)
+ drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
+ drawable.draw(canvas)
+ }
+ }
+ val p = MediaNotificationProcessor.generateArtworkPaletteBuilder(artworkBitmap)
+ .generate()
+ val swatch = MediaNotificationProcessor.findBackgroundSwatch(p)
+ bgColor = swatch.rgb
+ fgColor = MediaNotificationProcessor.selectForegroundColor(bgColor, p)
+ }
+ // Make sure colors will be legible
+ val isDark = !ContrastColorUtil.isColorLight(bgColor)
+ fgColor = ContrastColorUtil.resolveContrastColor(context, fgColor, bgColor,
+ isDark)
+ fgColor = ContrastColorUtil.ensureTextContrast(fgColor, bgColor, isDark)
+
+ // App name
+ val builder = Notification.Builder.recoverBuilder(context, notif)
+ val app = builder.loadHeaderAppName()
+
+ // App Icon
+ val smallIconDrawable: Drawable = sbn.notification.smallIcon.loadDrawable(context)
+
+ // Song name
+ var song: CharSequence? = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
+ if (song == null) {
+ song = metadata.getString(MediaMetadata.METADATA_KEY_TITLE)
+ }
+ if (song == null) {
+ song = HybridGroupManager.resolveTitle(notif)
+ }
+
+ // Artist name
+ var artist: CharSequence? = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
+ if (artist == null) {
+ artist = HybridGroupManager.resolveText(notif)
+ }
+
+ // Control buttons
+ val actionIcons: MutableList<MediaAction> = ArrayList()
+ val actions = notif.actions
+ val actionsToShowCollapsed = notif.extras.getIntArray(
+ Notification.EXTRA_COMPACT_ACTIONS)?.toList() ?: emptyList()
+ // TODO: b/153736623 look into creating actions when this isn't a media style notification
+
+ val packageContext: Context = sbn.getPackageContext(context)
+ for (action in actions) {
+ val mediaAction = MediaAction(
+ action.getIcon().loadDrawable(packageContext),
+ action.actionIntent,
+ action.title)
+ actionIcons.add(mediaAction)
+ }
+
+ foregroundExcecutor.execute {
+ onMediaDataLoaded(key, MediaData(true, fgColor, bgColor, app, smallIconDrawable, artist,
+ song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
+ notif.contentIntent))
+ }
+ }
+
+ /**
+ * Load a bitmap from the various Art metadata URIs
+ */
+ private fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
+ for (uri in ART_URIS) {
+ val uriString = metadata.getString(uri)
+ if (!TextUtils.isEmpty(uriString)) {
+ val albumArt = loadBitmapFromUri(Uri.parse(uriString))
+ if (albumArt != null) {
+ Log.d(TAG, "loaded art from $uri")
+ break
+ }
+ }
+ }
+ return null
+ }
+
+ /**
+ * Load a bitmap from a URI
+ * @param uri the uri to load
+ * @return bitmap, or null if couldn't be loaded
+ */
+ private fun loadBitmapFromUri(uri: Uri): Bitmap? {
+ // ImageDecoder requires a scheme of the following types
+ if (uri.scheme == null) {
+ return null
+ }
+
+ if (!uri.scheme.equals(ContentResolver.SCHEME_CONTENT) &&
+ !uri.scheme.equals(ContentResolver.SCHEME_ANDROID_RESOURCE) &&
+ !uri.scheme.equals(ContentResolver.SCHEME_FILE)) {
+ return null
+ }
+
+ val source = ImageDecoder.createSource(context.getContentResolver(), uri)
+ return try {
+ ImageDecoder.decodeBitmap(source)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ null
+ }
+ }
+
+ fun onMediaDataLoaded(key: String, data: MediaData) {
+ if (mediaEntries.containsKey(key)) {
+ // Otherwise this was removed already
+ mediaEntries.put(key, data)
+ listeners.forEach {
+ it.onMediaDataLoaded(key, data)
+ }
+ }
+ }
+
+ fun onNotificationRemoved(key: String) {
+ val removed = mediaEntries.remove(key)
+ if (removed != null) {
+ listeners.forEach {
+ it.onMediaDataRemoved(key)
+ }
+ }
+ }
+
+ private fun isMediaNotification(sbn: StatusBarNotification): Boolean {
+ if (!Utils.useQsMediaPlayer(context)) {
+ return false
+ }
+ if (!sbn.notification.hasMediaSession()) {
+ return false
+ }
+ val notificationStyle = sbn.notification.notificationStyle
+ if (Notification.DecoratedMediaCustomViewStyle::class.java.equals(notificationStyle) ||
+ Notification.MediaStyle::class.java.equals(notificationStyle)) {
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Are there any media notifications active?
+ */
+ fun hasActiveMedia() = mediaEntries.size > 0
+
+ fun hasAnyMedia(): Boolean {
+ // TODO: implement this when we implemented resumption
+ return hasActiveMedia()
+ }
+
+ interface Listener {
+
+ /**
+ * Called whenever there's new MediaData Loaded for the consumption in views
+ */
+ fun onMediaDataLoaded(key: String, data: MediaData) {}
+
+ /**
+ * Called whenever a previously existing Media notification was removed
+ */
+ fun onMediaDataRemoved(key: String) {}
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
new file mode 100644
index 0000000..6b1c520
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.annotation.IntDef
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroupOverlay
+import com.android.systemui.Interpolators
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.animation.UniqueObjectHostView
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * This manager is responsible for placement of the unique media view between the different hosts
+ * and animate the positions of the views to achieve seamless transitions.
+ */
+@Singleton
+class MediaHierarchyManager @Inject constructor(
+ private val context: Context,
+ private val statusBarStateController: SysuiStatusBarStateController,
+ private val keyguardStateController: KeyguardStateController,
+ private val bypassController: KeyguardBypassController,
+ private val mediaViewManager: MediaViewManager,
+ private val mediaMeasurementProvider: MediaMeasurementManager
+) {
+ /**
+ * The root overlay of the hierarchy. This is where the media notification is attached to
+ * whenever the view is transitioning from one host to another. It also make sure that the
+ * view is always in its final state when it is attached to a view host.
+ */
+ private var rootOverlay: ViewGroupOverlay? = null
+ private lateinit var currentState: MediaState
+ private val mediaCarousel
+ get() = mediaViewManager.mediaCarousel
+ private var animationStartState: MediaState? = null
+ private var statusbarState: Int = statusBarStateController.state
+ private var animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply {
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
+ addUpdateListener {
+ updateTargetState()
+ applyState(animationStartState!!.interpolate(targetState!!, animatedFraction))
+ }
+ addListener(object : AnimatorListenerAdapter() {
+ private var cancelled: Boolean = false
+
+ override fun onAnimationCancel(animation: Animator?) {
+ cancelled = true
+ }
+ override fun onAnimationEnd(animation: Animator?) {
+ if (!cancelled) {
+ applyTargetStateIfNotAnimating()
+ }
+ }
+
+ override fun onAnimationStart(animation: Animator?) {
+ cancelled = false
+ }
+ })
+ }
+ private var targetState: MediaState? = null
+ private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_LOCKSCREEN + 1)
+
+ /**
+ * The last location where this view was at before going to the desired location. This is
+ * useful for guided transitions.
+ */
+ @MediaLocation private var previousLocation = -1
+
+ /**
+ * The desired location where the view will be at the end of the transition.
+ */
+ @MediaLocation private var desiredLocation = -1
+
+ /**
+ * The current attachment location where the view is currently attached.
+ * Usually this matches the desired location except for animations whenever a view moves
+ * to the new desired location, during which it is in [IN_OVERLAY].
+ */
+ @MediaLocation private var currentAttachmentLocation = -1
+
+ var qsExpansion: Float = 0.0f
+ set(value) {
+ if (field != value) {
+ field = value
+ updateDesiredLocation()
+ if (getQSTransformationProgress() >= 0) {
+ updateTargetState()
+ applyTargetStateIfNotAnimating()
+ }
+ }
+ }
+
+ init {
+ statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
+ override fun onStatePreChange(oldState: Int, newState: Int) {
+ // We're updating the location before the state change happens, since we want the
+ // location of the previous state to still be up to date when the animation starts
+ statusbarState = newState
+ updateDesiredLocation()
+ }
+
+ override fun onStateChanged(newState: Int) {
+ updateTargetState()
+ }
+ })
+ }
+
+ /**
+ * Register a media host and create a view can be attached to a view hierarchy
+ * and where the players will be placed in when the host is the currently desired state.
+ *
+ * @return the hostView associated with this location
+ */
+ fun register(mediaObject: MediaHost) : ViewGroup {
+ val viewHost = createUniqueObjectHost(mediaObject)
+ mediaObject.hostView = viewHost;
+ mediaHosts[mediaObject.location] = mediaObject
+ if (mediaObject.location == desiredLocation) {
+ // In case we are overriding a view that is already visible, make sure we attach it
+ // to this new host view in the below call
+ desiredLocation = -1
+ }
+ if (mediaObject.location == currentAttachmentLocation) {
+ currentAttachmentLocation = -1
+ }
+ updateDesiredLocation()
+ return viewHost
+ }
+
+ private fun createUniqueObjectHost(host: MediaHost): UniqueObjectHostView {
+ val viewHost = UniqueObjectHostView(context)
+ viewHost.measurementCache = mediaMeasurementProvider.obtainCache(host)
+ viewHost.onMeasureListener = { input ->
+ if (host.location == desiredLocation) {
+ // Measurement of the currently active player is happening, Let's make
+ // sure the player width is up to date
+ val measuringInput = host.getMeasuringInput(input)
+ mediaViewManager.setPlayerWidth(measuringInput.width)
+ }
+ }
+
+ viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(p0: View?) {
+ if (rootOverlay == null) {
+ rootOverlay = (viewHost.viewRootImpl.view.overlay as ViewGroupOverlay)
+ }
+ viewHost.removeOnAttachStateChangeListener(this)
+ }
+
+ override fun onViewDetachedFromWindow(p0: View?) {
+ }
+ })
+ return viewHost
+ }
+
+ /**
+ * Updates the location that the view should be in. If it changes, an animation may be triggered
+ * going from the old desired location to the new one.
+ */
+ private fun updateDesiredLocation() {
+ val desiredLocation = calculateLocation()
+ if (desiredLocation != this.desiredLocation) {
+ if (this.desiredLocation >= 0) {
+ previousLocation = this.desiredLocation
+ }
+ val isNewView = this.desiredLocation == -1
+ this.desiredLocation = desiredLocation
+ // Let's perform a transition
+ val animate = shouldAnimateTransition(desiredLocation, previousLocation)
+ val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
+ mediaViewManager.onDesiredLocationChanged(getHost(desiredLocation)?.currentState,
+ animate, animDuration, delay)
+ performTransitionToNewLocation(isNewView, animate)
+ }
+ }
+
+ private fun performTransitionToNewLocation(isNewView: Boolean, animate: Boolean) {
+ if (previousLocation < 0 || isNewView) {
+ cancelAnimationAndApplyDesiredState()
+ return
+ }
+ val currentHost = getHost(desiredLocation)
+ val previousHost = getHost(previousLocation)
+ if (currentHost == null || previousHost == null) {
+ cancelAnimationAndApplyDesiredState()
+ return
+ }
+ updateTargetState()
+ if (isCurrentlyInGuidedTransformation()) {
+ applyTargetStateIfNotAnimating()
+ } else if (animate) {
+ animator.cancel()
+ if (currentAttachmentLocation == IN_OVERLAY
+ || !previousHost.hostView.isAttachedToWindow) {
+ // Let's animate to the new position, starting from the current position
+ // We also go in here in case the view was detached, since the bounds wouldn't
+ // be correct anymore
+ animationStartState = currentState.copy()
+ } else {
+ // otherwise, let's take the freshest state, since the current one could
+ // be outdated
+ animationStartState = previousHost.currentState.copy()
+ }
+ adjustAnimatorForTransition(desiredLocation, previousLocation)
+ animator.start()
+ } else {
+ cancelAnimationAndApplyDesiredState()
+ }
+ }
+
+ private fun shouldAnimateTransition(
+ @MediaLocation currentLocation: Int,
+ @MediaLocation previousLocation: Int
+ ): Boolean {
+ if (currentLocation == LOCATION_QQS
+ && previousLocation == LOCATION_LOCKSCREEN
+ && (statusBarStateController.leaveOpenOnKeyguardHide()
+ || statusbarState == StatusBarState.SHADE_LOCKED)) {
+ // Usually listening to the isShown is enough to determine this, but there is some
+ // non-trivial reattaching logic happening that will make the view not-shown earlier
+ return true
+ }
+ return mediaCarousel.isShown || animator.isRunning
+ }
+
+ private fun adjustAnimatorForTransition(desiredLocation: Int, previousLocation: Int) {
+ val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
+ animator.apply {
+ duration = animDuration
+ startDelay = delay
+ }
+
+ }
+
+ private fun getAnimationParams(previousLocation: Int, desiredLocation: Int): Pair<Long, Long> {
+ var animDuration = 200L
+ var delay = 0L
+ if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QQS) {
+ // Going to the full shade, let's adjust the animation duration
+ if (statusbarState == StatusBarState.SHADE
+ && keyguardStateController.isKeyguardFadingAway) {
+ delay = keyguardStateController.keyguardFadingAwayDelay
+ }
+ animDuration = StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE.toLong()
+ } else if (previousLocation == LOCATION_QQS && desiredLocation == LOCATION_LOCKSCREEN) {
+ animDuration = StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR.toLong()
+ }
+ return animDuration to delay
+ }
+
+ private fun applyTargetStateIfNotAnimating() {
+ if (!animator.isRunning) {
+ // Let's immediately apply the target state (which is interpolated) if there is
+ // no animation running. Otherwise the animation update will already update
+ // the location
+ applyState(targetState!!)
+ }
+ }
+
+ /**
+ * Updates the state that the view wants to be in at the end of the animation.
+ */
+ private fun updateTargetState() {
+ if (isCurrentlyInGuidedTransformation()) {
+ val progress = getTransformationProgress()
+ val currentHost = getHost(desiredLocation)!!
+ val previousHost = getHost(previousLocation)!!
+ val newState = currentHost.currentState
+ val previousState = previousHost.currentState
+ targetState = previousState.interpolate(newState, progress)
+ } else {
+ targetState = getHost(desiredLocation)?.currentState
+ }
+ }
+
+ /**
+ * @return true if this transformation is guided by an external progress like a finger
+ */
+ private fun isCurrentlyInGuidedTransformation() : Boolean {
+ return getTransformationProgress() >= 0
+ }
+
+ /**
+ * @return the current transformation progress if we're in a guided transformation and -1
+ * otherwise
+ */
+ private fun getTransformationProgress(): Float {
+ val progress = getQSTransformationProgress()
+ if (progress >= 0) {
+ return progress
+ }
+ return -1.0f
+ }
+
+ private fun getQSTransformationProgress(): Float {
+ val currentHost = getHost(desiredLocation)
+ val previousHost = getHost(previousLocation)
+ if (currentHost?.location == LOCATION_QS) {
+ if (previousHost?.location == LOCATION_QQS) {
+ return qsExpansion
+ }
+ }
+ return -1.0f
+ }
+
+ private fun getHost(@MediaLocation location: Int): MediaHost? {
+ if (location < 0) {
+ return null
+ }
+ return mediaHosts[location]
+ }
+
+ private fun cancelAnimationAndApplyDesiredState() {
+ animator.cancel()
+ getHost(desiredLocation)?.let {
+ applyState(it.currentState)
+ }
+ }
+
+ private fun applyState(state: MediaState) {
+ currentState = state.copy()
+ mediaViewManager.setCurrentState(currentState)
+ updateHostAttachment()
+ if (currentAttachmentLocation == IN_OVERLAY) {
+ val boundsOnScreen = state.boundsOnScreen
+ mediaCarousel.setLeftTopRightBottom(
+ boundsOnScreen.left,
+ boundsOnScreen.top,
+ boundsOnScreen.right,
+ boundsOnScreen.bottom)
+ }
+ }
+
+ private fun updateHostAttachment() {
+ val inOverlay = isTransitionRunning() && rootOverlay != null
+ val newLocation = if (inOverlay) IN_OVERLAY else desiredLocation
+ if (currentAttachmentLocation != newLocation) {
+ currentAttachmentLocation = newLocation
+
+ // Remove the carousel from the old host
+ (mediaCarousel.parent as ViewGroup?)?.removeView(mediaCarousel)
+
+ // Add it to the new one
+ val targetHost = getHost(desiredLocation)!!.hostView
+ if (inOverlay) {
+ rootOverlay!!.add(mediaCarousel)
+ } else {
+ targetHost.addView(mediaCarousel)
+ mediaViewManager.onViewReattached()
+ }
+ }
+ }
+
+ private fun isTransitionRunning(): Boolean {
+ return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f
+ || animator.isRunning
+ }
+
+ @MediaLocation
+ private fun calculateLocation() : Int {
+ val onLockscreen = (!bypassController.bypassEnabled
+ && (statusbarState == StatusBarState.KEYGUARD
+ || statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
+ return when {
+ qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS
+ qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
+ onLockscreen -> LOCATION_LOCKSCREEN
+ else -> LOCATION_QQS
+ }
+ }
+
+ /**
+ * The expansion of quick settings
+ */
+ @IntDef(prefix = ["LOCATION_"], value = [LOCATION_QS, LOCATION_QQS, LOCATION_LOCKSCREEN])
+ @Retention(AnnotationRetention.SOURCE)
+ annotation class MediaLocation
+
+ companion object {
+ /**
+ * Attached in expanded quick settings
+ */
+ const val LOCATION_QS = 0
+
+ /**
+ * Attached in the collapsed QS
+ */
+ const val LOCATION_QQS = 1
+
+ /**
+ * Attached on the lock screen
+ */
+ const val LOCATION_LOCKSCREEN = 2
+
+ /**
+ * Attached at the root of the hierarchy in an overlay
+ */
+ const val IN_OVERLAY = -1000
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
new file mode 100644
index 0000000..6e7b6bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -0,0 +1,159 @@
+package com.android.systemui.media
+
+import android.graphics.Rect
+import android.util.MathUtils
+import android.view.View
+import android.view.View.OnAttachStateChangeListener
+import android.view.ViewGroup
+import com.android.systemui.media.MediaHierarchyManager.MediaLocation
+import com.android.systemui.util.animation.MeasurementInput
+import javax.inject.Inject
+
+class MediaHost @Inject constructor(
+ private val state: MediaHostState,
+ private val mediaHierarchyManager: MediaHierarchyManager,
+ private val mediaDataManager: MediaDataManager
+) : MediaState by state {
+ lateinit var hostView: ViewGroup
+ var location: Int = -1
+ private set
+ var visibleChangedListener: ((Boolean) -> Unit)? = null
+ var visible: Boolean = false
+ private set
+
+ private val tmpLocationOnScreen: IntArray = intArrayOf(0, 0)
+
+ /**
+ * Get the current Media state. This also updates the location on screen
+ */
+ val currentState : MediaState
+ get () {
+ hostView.getLocationOnScreen(tmpLocationOnScreen)
+ var left = tmpLocationOnScreen[0] + hostView.paddingLeft
+ var top = tmpLocationOnScreen[1] + hostView.paddingTop
+ var right = tmpLocationOnScreen[0] + hostView.width - hostView.paddingRight
+ var bottom = tmpLocationOnScreen[1] + hostView.height - hostView.paddingBottom
+ // Handle cases when the width or height is 0 but it has padding. In those cases
+ // the above could return negative widths, which is wrong
+ if (right < left) {
+ left = 0
+ right = 0;
+ }
+ if (bottom < top) {
+ bottom = 0
+ top = 0;
+ }
+ state.boundsOnScreen.set(left, top, right, bottom)
+ return state
+ }
+
+ private val listener = object : MediaDataManager.Listener {
+ override fun onMediaDataLoaded(key: String, data: MediaData) {
+ updateViewVisibility()
+ }
+
+ override fun onMediaDataRemoved(key: String) {
+ updateViewVisibility()
+ }
+ }
+
+ /**
+ * Initialize this MediaObject and create a host view.
+ *
+ * @param location the location this host name has. Used to identify the host during
+ * transitions.
+ */
+ fun init(@MediaLocation location: Int) {
+ this.location = location;
+ hostView = mediaHierarchyManager.register(this)
+ hostView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View?) {
+ mediaDataManager.addListener(listener)
+ updateViewVisibility()
+ }
+
+ override fun onViewDetachedFromWindow(v: View?) {
+ mediaDataManager.removeListener(listener)
+ }
+ })
+ updateViewVisibility()
+ }
+
+ private fun updateViewVisibility() {
+ if (showsOnlyActiveMedia) {
+ visible = mediaDataManager.hasActiveMedia()
+ } else {
+ visible = mediaDataManager.hasAnyMedia()
+ }
+ hostView.visibility = if (visible) View.VISIBLE else View.GONE
+ visibleChangedListener?.invoke(visible)
+ }
+
+ class MediaHostState @Inject constructor() : MediaState {
+ var measurementInput: MediaMeasurementInput? = null
+ override var expansion: Float = 0.0f
+ override var showsOnlyActiveMedia: Boolean = false
+ override val boundsOnScreen: Rect = Rect()
+
+ override fun copy() : MediaState {
+ val mediaHostState = MediaHostState()
+ mediaHostState.expansion = expansion
+ mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
+ mediaHostState.boundsOnScreen.set(boundsOnScreen)
+ mediaHostState.measurementInput = measurementInput
+ return mediaHostState
+ }
+
+ override fun interpolate(other: MediaState, amount: Float) : MediaState {
+ val result = MediaHostState()
+ result.expansion = MathUtils.lerp(expansion, other.expansion, amount)
+ val left = MathUtils.lerp(boundsOnScreen.left.toFloat(),
+ other.boundsOnScreen.left.toFloat(), amount).toInt()
+ val top = MathUtils.lerp(boundsOnScreen.top.toFloat(),
+ other.boundsOnScreen.top.toFloat(), amount).toInt()
+ val right = MathUtils.lerp(boundsOnScreen.right.toFloat(),
+ other.boundsOnScreen.right.toFloat(), amount).toInt()
+ val bottom = MathUtils.lerp(boundsOnScreen.bottom.toFloat(),
+ other.boundsOnScreen.bottom.toFloat(), amount).toInt()
+ result.boundsOnScreen.set(left, top, right, bottom)
+ result.showsOnlyActiveMedia = other.showsOnlyActiveMedia || showsOnlyActiveMedia
+ if (amount > 0.0f) {
+ if (other is MediaHostState) {
+ result.measurementInput = other.measurementInput
+ }
+ } else {
+ result.measurementInput
+ }
+ return result
+ }
+
+ override fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput {
+ measurementInput = MediaMeasurementInput(input, expansion)
+ return measurementInput as MediaMeasurementInput
+ }
+ }
+}
+
+interface MediaState {
+ var expansion: Float
+ var showsOnlyActiveMedia: Boolean
+ val boundsOnScreen: Rect
+ fun copy() : MediaState
+ fun interpolate(other: MediaState, amount: Float) : MediaState
+ fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput
+}
+/**
+ * The measurement input for a Media View
+ */
+data class MediaMeasurementInput(
+ private val viewInput: MeasurementInput,
+ val expansion: Float) : MeasurementInput by viewInput {
+
+ override fun sameAs(input: MeasurementInput?): Boolean {
+ if (!(input is MediaMeasurementInput)) {
+ return false
+ }
+ return width == input.width && expansion == input.expansion
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaMeasurementManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaMeasurementManager.kt
new file mode 100644
index 0000000..4bbf5eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaMeasurementManager.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.media
+
+import com.android.systemui.util.animation.BaseMeasurementCache
+import com.android.systemui.util.animation.GuaranteedMeasurementCache
+import com.android.systemui.util.animation.MeasurementCache
+import com.android.systemui.util.animation.MeasurementInput
+import com.android.systemui.util.animation.MeasurementOutput
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * A class responsible creating measurement caches for media hosts which also coordinates with
+ * the view manager to obtain sizes for unknown measurement inputs.
+ */
+@Singleton
+class MediaMeasurementManager @Inject constructor(
+ private val mediaViewManager: MediaViewManager
+) {
+ private val baseCache: MeasurementCache
+
+ init {
+ baseCache = BaseMeasurementCache()
+ }
+
+ private fun provideMeasurement(input: MediaMeasurementInput) : MeasurementOutput? {
+ return mediaViewManager.obtainMeasurement(input)
+ }
+
+ /**
+ * Obtain a guaranteed measurement cache for a host view. The measurement cache makes sure that
+ * requesting any size from the cache will always return the correct value.
+ */
+ fun obtainCache(host: MediaState): GuaranteedMeasurementCache {
+ val remapper = { input: MeasurementInput ->
+ host.getMeasuringInput(input)
+ }
+ val provider = { input: MeasurementInput ->
+ provideMeasurement(input as MediaMeasurementInput)
+ }
+ return GuaranteedMeasurementCache(baseCache, remapper, provider)
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
new file mode 100644
index 0000000..9c92b0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
@@ -0,0 +1,314 @@
+package com.android.systemui.media
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.HorizontalScrollView
+import android.widget.LinearLayout
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.media.InfoMediaManager
+import com.android.settingslib.media.LocalMediaManager
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.notification.VisualStabilityManager
+import com.android.systemui.util.animation.MeasurementOutput
+import com.android.systemui.util.animation.UniqueObjectHostView
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Class that is responsible for keeping the view carousel up to date.
+ * This also handles changes in state and applies them to the media carousel like the expansion.
+ */
+@Singleton
+class MediaViewManager @Inject constructor(
+ private val context: Context,
+ @Main private val foregroundExecutor: Executor,
+ @Background private val backgroundExecutor: DelayableExecutor,
+ private val localBluetoothManager: LocalBluetoothManager?,
+ private val visualStabilityManager: VisualStabilityManager,
+ private val activityStarter: ActivityStarter,
+ mediaManager: MediaDataManager
+) {
+ private var playerWidth: Int = 0
+ private var playerWidthPlusPadding: Int = 0
+ private var desiredState: MediaHost.MediaHostState? = null
+ private var currentState: MediaState? = null
+ val mediaCarousel: HorizontalScrollView
+ private val mediaContent: ViewGroup
+ private val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
+ private val visualStabilityCallback = ::reorderAllPlayers
+ private var activeMediaIndex: Int = 0
+ private var scrollIntoCurrentMedia: Int = 0
+
+ private var currentlyExpanded = true
+ set(value) {
+ if (field != value) {
+ field = value
+ for (player in mediaPlayers.values) {
+ player.setListening(field)
+ }
+ }
+ }
+ private val scrollChangedListener = object : View.OnScrollChangeListener {
+ override fun onScrollChange(
+ v: View?,
+ scrollX: Int,
+ scrollY: Int,
+ oldScrollX: Int,
+ oldScrollY: Int
+ ) {
+ if (playerWidthPlusPadding == 0) {
+ return
+ }
+ onMediaScrollingChanged(scrollX / playerWidthPlusPadding,
+ scrollX % playerWidthPlusPadding)
+ }
+ }
+
+ init {
+ mediaCarousel = inflateMediaCarousel()
+ mediaCarousel.setOnScrollChangeListener(scrollChangedListener)
+ mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
+ mediaManager.addListener(object : MediaDataManager.Listener {
+ override fun onMediaDataLoaded(key: String, data: MediaData) {
+ updateView(key, data)
+ updatePlayerVisibilities()
+ }
+
+ override fun onMediaDataRemoved(key: String) {
+ val removed = mediaPlayers.remove(key)
+ removed?.apply {
+ val beforeActive = mediaContent.indexOfChild(removed.view?.player) <=
+ activeMediaIndex
+ mediaContent.removeView(removed.view?.player)
+ removed.onDestroy()
+ updateMediaPaddings()
+ if (beforeActive) {
+ // also update the index here since the scroll below might not always lead
+ // to a scrolling changed
+ activeMediaIndex = Math.max(0, activeMediaIndex - 1)
+ mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX -
+ playerWidthPlusPadding, 0)
+ }
+ updatePlayerVisibilities()
+ }
+ }
+ })
+ }
+
+ private fun inflateMediaCarousel(): HorizontalScrollView {
+ return LayoutInflater.from(context).inflate(R.layout.media_carousel,
+ UniqueObjectHostView(context), false) as HorizontalScrollView
+ }
+
+ private fun reorderAllPlayers() {
+ for (mediaPlayer in mediaPlayers.values) {
+ val view = mediaPlayer.view?.player
+ if (mediaPlayer.isPlaying && mediaContent.indexOfChild(view) != 0) {
+ mediaContent.removeView(view)
+ mediaContent.addView(view, 0)
+ }
+ }
+ updateMediaPaddings()
+ updatePlayerVisibilities()
+ }
+
+ private fun onMediaScrollingChanged(newIndex: Int, scrollInAmount: Int) {
+ val wasScrolledIn = scrollIntoCurrentMedia != 0
+ scrollIntoCurrentMedia = scrollInAmount
+ val nowScrolledIn = scrollIntoCurrentMedia != 0
+ if (newIndex != activeMediaIndex || wasScrolledIn != nowScrolledIn) {
+ activeMediaIndex = newIndex
+ updatePlayerVisibilities()
+ }
+ }
+
+ private fun updatePlayerVisibilities() {
+ val scrolledIn = scrollIntoCurrentMedia != 0
+ for (i in 0 until mediaContent.childCount) {
+ val view = mediaContent.getChildAt(i)
+ val visible = (i == activeMediaIndex) || ((i == (activeMediaIndex + 1)) && scrolledIn)
+ view.visibility = if (visible) View.VISIBLE else View.INVISIBLE
+ }
+ }
+
+ private fun updateView(key: String, data: MediaData) {
+ var existingPlayer = mediaPlayers[key]
+ if (existingPlayer == null) {
+ // Set up listener for device changes
+ // TODO: integrate with MediaTransferManager?
+ val imm = InfoMediaManager(context, data.packageName,
+ null /* notification */, localBluetoothManager)
+ val routeManager = LocalMediaManager(context, localBluetoothManager,
+ imm, data.packageName)
+
+ existingPlayer = MediaControlPanel(context, routeManager, foregroundExecutor,
+ backgroundExecutor, activityStarter)
+ existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context),
+ mediaContent))
+ mediaPlayers[key] = existingPlayer
+ val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT)
+ existingPlayer.view?.player?.setLayoutParams(lp)
+ existingPlayer.setListening(currentlyExpanded)
+ if (existingPlayer.isPlaying) {
+ mediaContent.addView(existingPlayer.view?.player, 0)
+ } else {
+ mediaContent.addView(existingPlayer.view?.player)
+ }
+ updatePlayerToCurrentState(existingPlayer)
+ } else if (existingPlayer.isPlaying &&
+ mediaContent.indexOfChild(existingPlayer.view?.player) != 0) {
+ if (visualStabilityManager.isReorderingAllowed) {
+ mediaContent.removeView(existingPlayer.view?.player)
+ mediaContent.addView(existingPlayer.view?.player, 0)
+ } else {
+ visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback)
+ }
+ }
+ existingPlayer.bind(data)
+ // Resetting the progress to make sure it's taken into account for the latest
+ // motion model
+ existingPlayer.view?.player?.progress = currentState?.expansion ?: 0.0f
+ updateMediaPaddings()
+ }
+
+ private fun updatePlayerToCurrentState(existingPlayer: MediaControlPanel) {
+ if (desiredState != null && desiredState!!.measurementInput != null) {
+ // make sure the player width is set to the current state
+ existingPlayer.setPlayerWidth(playerWidth)
+ }
+ }
+
+ private fun updateMediaPaddings() {
+ val padding = context.resources.getDimensionPixelSize(R.dimen.qs_media_padding)
+ val childCount = mediaContent.childCount
+ for (i in 0 until childCount) {
+ val mediaView = mediaContent.getChildAt(i)
+ val desiredPaddingEnd = if (i == childCount - 1) 0 else padding
+ val layoutParams = mediaView.layoutParams as ViewGroup.MarginLayoutParams
+ if (layoutParams.marginEnd != desiredPaddingEnd) {
+ layoutParams.marginEnd = desiredPaddingEnd
+ mediaView.layoutParams = layoutParams
+ }
+ }
+ }
+
+ /**
+ * Set the current state of a view. This is updated often during animations and we shouldn't
+ * do anything expensive.
+ */
+ fun setCurrentState(state: MediaState) {
+ currentState = state
+ currentlyExpanded = state.expansion > 0
+ for (mediaPlayer in mediaPlayers.values) {
+ val view = mediaPlayer.view?.player
+ view?.progress = state.expansion
+ }
+ }
+
+ /**
+ * The desired location of this view has changed. We should remeasure the view to match
+ * the new bounds and kick off bounds animations if necessary.
+ * If an animation is happening, an animation is kicked of externally, which sets a new
+ * current state until we reach the targetState.
+ *
+ * @param desiredState the target state we're transitioning to
+ * @param animate should this be animated
+ */
+ fun onDesiredLocationChanged(
+ desiredState: MediaState?,
+ animate: Boolean,
+ duration: Long,
+ startDelay: Long
+ ) {
+ if (desiredState is MediaHost.MediaHostState) {
+ // This is a hosting view, let's remeasure our players
+ this.desiredState = desiredState
+ val width = desiredState.boundsOnScreen.width()
+ if (playerWidth != width) {
+ setPlayerWidth(width)
+ for (mediaPlayer in mediaPlayers.values) {
+ if (animate && mediaPlayer.view?.player?.visibility == View.VISIBLE) {
+ mediaPlayer.animatePendingSizeChange(duration, startDelay)
+ }
+ }
+ val widthSpec = desiredState.measurementInput?.widthMeasureSpec ?: 0
+ val heightSpec = desiredState.measurementInput?.heightMeasureSpec ?: 0
+ var left = 0
+ for (i in 0 until mediaContent.childCount) {
+ val view = mediaContent.getChildAt(i)
+ view.measure(widthSpec, heightSpec)
+ view.layout(left, 0, left + width, view.measuredHeight)
+ left = left + playerWidthPlusPadding
+ }
+ }
+ }
+ }
+
+ fun setPlayerWidth(width: Int) {
+ if (width != playerWidth) {
+ playerWidth = width
+ playerWidthPlusPadding = playerWidth + context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_padding)
+ for (mediaPlayer in mediaPlayers.values) {
+ mediaPlayer.setPlayerWidth(width)
+ }
+ // The player width has changed, let's update the scroll position to make sure
+ // it's still at the same place
+ var newScroll = activeMediaIndex * playerWidthPlusPadding
+ if (scrollIntoCurrentMedia > playerWidthPlusPadding) {
+ newScroll += playerWidthPlusPadding
+ - (scrollIntoCurrentMedia - playerWidthPlusPadding)
+ } else {
+ newScroll += scrollIntoCurrentMedia
+ }
+ mediaCarousel.scrollX = newScroll
+ }
+ }
+
+ /**
+ * Get a measurement for the given input state. This measures the first player and returns
+ * its bounds as if it were measured with the given measurement dimensions
+ */
+ fun obtainMeasurement(input: MediaMeasurementInput): MeasurementOutput? {
+ val firstPlayer = mediaPlayers.values.firstOrNull() ?: return null
+ var result: MeasurementOutput? = null
+ firstPlayer.view?.player?.let {
+ // Let's measure the size of the first player and return its height
+ val previousProgress = it.progress
+ val previousRight = it.right
+ val previousBottom = it.bottom
+ it.progress = input.expansion
+ firstPlayer.measure(input)
+ // Relayouting is necessary in motionlayout to obtain its size properly ....
+ it.layout(0, 0, it.measuredWidth, it.measuredHeight)
+ val result = MeasurementOutput(it.measuredWidth, it.measuredHeight)
+ it.progress = previousProgress
+ if (desiredState != null) {
+ // remeasure it to the old size again!
+ firstPlayer.measure(desiredState!!.measurementInput)
+ it.layout(0, 0, previousRight, previousBottom)
+ }
+ }
+ return result
+ }
+
+ fun onViewReattached() {
+ if (desiredState is MediaHost.MediaHostState) {
+ // HACK: MotionLayout doesn't always properly reevalate the state, let's kick of
+ // a measure to force it.
+ val widthSpec = desiredState!!.measurementInput?.widthMeasureSpec ?: 0
+ val heightSpec = desiredState!!.measurementInput?.heightMeasureSpec ?: 0
+ for (mediaPlayer in mediaPlayers.values) {
+ mediaPlayer.view?.player?.measure(widthSpec, heightSpec)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
new file mode 100644
index 0000000..571e18d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.SeekBar
+import android.widget.TextView
+
+import androidx.constraintlayout.motion.widget.MotionLayout
+
+import com.android.systemui.R
+
+/**
+ * ViewHolder for a media player.
+ */
+class PlayerViewHolder private constructor(itemView: View) {
+
+ val player = itemView as MotionLayout
+ val background = itemView.requireViewById<View>(R.id.media_background)
+
+ // Player information
+ val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
+ val appName = itemView.requireViewById<TextView>(R.id.app_name)
+ val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
+ val titleText = itemView.requireViewById<TextView>(R.id.header_title)
+ val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
+
+ // Output switcher
+ val seamless = itemView.findViewById<ViewGroup>(R.id.media_seamless)
+ val seamlessIcon = itemView.requireViewById<ImageView>(R.id.media_seamless_image)
+ val seamlessText = itemView.requireViewById<TextView>(R.id.media_seamless_text)
+
+ // Seek bar
+ val seekBar = itemView.requireViewById<SeekBar>(R.id.media_progress_bar)
+ val elapsedTimeView = itemView.requireViewById<TextView>(R.id.media_elapsed_time)
+ val totalTimeView = itemView.requireViewById<TextView>(R.id.media_total_time)
+
+ // Action Buttons
+ val action0 = itemView.requireViewById<ImageButton>(R.id.action0)
+ val action1 = itemView.requireViewById<ImageButton>(R.id.action1)
+ val action2 = itemView.requireViewById<ImageButton>(R.id.action2)
+ val action3 = itemView.requireViewById<ImageButton>(R.id.action3)
+ val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
+
+ fun getAction(id: Int): ImageButton {
+ return when (id) {
+ R.id.action0 -> action0
+ R.id.action1 -> action1
+ R.id.action2 -> action2
+ R.id.action3 -> action3
+ R.id.action4 -> action4
+ else -> {
+ throw IllegalArgumentException()
+ }
+ }
+ }
+
+ // Settings screen
+ val options = itemView.requireViewById<View>(R.id.qs_media_controls_options)
+
+ companion object {
+ /**
+ * Creates a PlayerViewHolder.
+ *
+ * @param inflater LayoutInflater to use to inflate the layout.
+ * @param parent Parent of inflated view.
+ */
+ @JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup): PlayerViewHolder {
+ val v = inflater.inflate(R.layout.qs_media_panel, parent, false)
+ return PlayerViewHolder(v)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/UnboundHorizontalScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/UnboundHorizontalScrollView.kt
new file mode 100644
index 0000000..8efc954
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/UnboundHorizontalScrollView.kt
@@ -0,0 +1,31 @@
+package com.android.systemui.media
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.HorizontalScrollView
+
+/**
+ * A Horizontal scrollview that doesn't limit itself to the childs bounds. This is useful
+ * when only measuring children but not the parent, when trying to apply a new scroll position
+ */
+class UnboundHorizontalScrollView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
+ : HorizontalScrollView(context, attrs, defStyleAttr) {
+
+ /**
+ * Allow all scrolls to go through, use base implementation
+ */
+ override fun scrollTo(x: Int, y: Int) {
+ if (mScrollX != x || mScrollY != y) {
+ val oldX: Int = mScrollX
+ val oldY: Int = mScrollY
+ mScrollX = x
+ mScrollY = y
+ invalidateParentCaches()
+ onScrollChanged(mScrollX, mScrollY, oldX, oldY)
+ if (!awakenScrollBars()) {
+ postInvalidateOnAnimation()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 8d6ce47..7e2efc0 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -552,6 +552,9 @@
? null : destinationBounds;
// As for the final windowing mode, simply reset it to undefined.
wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+ if (mSplitDivider != null && direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN) {
+ wct.reparent(mToken, mSplitDivider.getSecondaryRoot(), true /* onTop */);
+ }
} else {
taskBounds = destinationBounds;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
index c5ae3ab..40d317c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -117,7 +117,7 @@
it.tileView.measure(exactly(smallTileSize), exactly(smallTileSize))
}
- val height = twoLineHeight
+ val height = twoLineHeight + paddingBottom + paddingTop
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index a0ea7fa..ce00229 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -269,14 +269,6 @@
count++;
}
-
- if (Utils.useQsMediaPlayer(mQsPanel.getContext())) {
- View qsMediaView = mQsPanel.getMediaPanel();
- View qqsMediaView = mQuickQsPanel.getMediaPlayer().getView();
- translationXBuilder.addFloat(qsMediaView, "alpha", 0, 1);
- translationXBuilder.addFloat(qqsMediaView, "alpha", 1, 0);
- }
-
if (mAllowFancy) {
// Make brightness appear static position and alpha in through second half.
View brightness = mQsPanel.getBrightnessView();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index be8a8fd..6b0775f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -42,7 +42,7 @@
private QuickStatusBarHeader mHeader;
private float mQsExpansion;
private QSCustomizer mQSCustomizer;
- private View mQSFooter;
+ private View mDragHandle;
private View mBackground;
private View mBackgroundGradient;
@@ -62,7 +62,7 @@
mQSDetail = findViewById(R.id.qs_detail);
mHeader = findViewById(R.id.header);
mQSCustomizer = findViewById(R.id.qs_customize);
- mQSFooter = findViewById(R.id.qs_footer);
+ mDragHandle = findViewById(R.id.qs_drag_handle_view);
mBackground = findViewById(R.id.quick_settings_background);
mStatusBarBackground = findViewById(R.id.quick_settings_status_bar_background);
mBackgroundGradient = findViewById(R.id.quick_settings_gradient_view);
@@ -167,8 +167,8 @@
int height = calculateContainerHeight();
setBottom(getTop() + height);
mQSDetail.setBottom(getTop() + height);
- // Pin QS Footer to the bottom of the panel.
- mQSFooter.setTranslationY(height - mQSFooter.getHeight());
+ // Pin the drag handle to the bottom of the panel.
+ mDragHandle.setTranslationY(height - mDragHandle.getHeight());
mBackground.setTop(mQSPanel.getTop());
mBackground.setBottom(height);
}
@@ -192,13 +192,13 @@
public void setExpansion(float expansion) {
mQsExpansion = expansion;
+ mDragHandle.setAlpha(1.0f - expansion);
updateExpansion();
}
private void setMargins() {
setMargins(mQSDetail);
setMargins(mBackground);
- setMargins(mQSFooter);
mQSPanel.setMargins(mSideMargins);
mHeader.setMargins(mSideMargins);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 5de6d1c..fc8e36f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -98,7 +98,6 @@
private TouchAnimator mSettingsCogAnimator;
private View mActionsContainer;
- private View mDragHandle;
private OnClickListener mExpandClickListener;
@@ -146,7 +145,6 @@
mMultiUserSwitch = findViewById(R.id.multi_user_switch);
mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
- mDragHandle = findViewById(R.id.qs_drag_handle_view);
mActionsContainer = findViewById(R.id.qs_footer_actions_container);
mEditContainer = findViewById(R.id.qs_footer_actions_edit_container);
@@ -219,7 +217,6 @@
return new TouchAnimator.Builder()
.addFloat(mActionsContainer, "alpha", 0, 1)
.addFloat(mEditContainer, "alpha", 0, 1)
- .addFloat(mDragHandle, "alpha", 1, 0, 0)
.addFloat(mPageIndicator, "alpha", 0, 1)
.setStartDelay(0.15f)
.build();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 5b09267..865fd07 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -37,6 +37,7 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.R.id;
+import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.customize.QSCustomizer;
@@ -47,6 +48,7 @@
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.util.InjectionInflationController;
import com.android.systemui.util.LifecycleFragment;
+import com.android.systemui.util.Utils;
import javax.inject.Inject;
@@ -91,6 +93,7 @@
*/
private int mState;
private QSContainerImplController mQSContainerImplController;
+ private int[] mTmpLocation = new int[2];
@Inject
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
@@ -377,8 +380,7 @@
mLastKeyguardAndExpanded = onKeyguardAndExpanded;
boolean fullyExpanded = expansion == 1;
- int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom()
- + mFooter.getHeight();
+ int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom();
float panelTranslationY = translationScaleY * heightDiff;
// Let the views animate their contents correctly by giving them the necessary context.
@@ -404,6 +406,32 @@
if (mQSAnimator != null) {
mQSAnimator.setPosition(expansion);
}
+ updateMediaPositions();
+ }
+
+ private void updateMediaPositions() {
+ if (Utils.useQsMediaPlayer(getContext())) {
+ mContainer.getLocationOnScreen(mTmpLocation);
+ float absoluteBottomPosition = mTmpLocation[1] + mContainer.getHeight();
+ pinToBottom(absoluteBottomPosition, mQSPanel.getMediaHost());
+ pinToBottom(absoluteBottomPosition - mHeader.getPaddingBottom(),
+ mHeader.getHeaderQsPanel().getMediaHost());
+ }
+ }
+
+ private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost) {
+ View hostView = mediaHost.getHostView();
+ if (mLastQSExpansion > 0) {
+ ViewGroup.MarginLayoutParams params =
+ (ViewGroup.MarginLayoutParams) hostView.getLayoutParams();
+ float targetPosition = absoluteBottomPosition - params.bottomMargin
+ - hostView.getHeight();
+ float currentPosition = mediaHost.getCurrentState().getBoundsOnScreen().top
+ - hostView.getTranslationY();
+ hostView.setTranslationY(targetPosition - currentPosition);
+ } else {
+ hostView.setTranslationY(0);
+ }
}
private boolean headerWillBeAnimating() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
deleted file mode 100644
index 174441b..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.ColorStateList;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.media.MediaDescription;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import com.android.settingslib.media.LocalMediaManager;
-import com.android.systemui.R;
-import com.android.systemui.media.IlluminationDrawable;
-import com.android.systemui.media.MediaControlPanel;
-import com.android.systemui.media.SeekBarObserver;
-import com.android.systemui.media.SeekBarViewModel;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import java.util.concurrent.Executor;
-
-/**
- * Single media player for carousel in QSPanel
- */
-public class QSMediaPlayer extends MediaControlPanel {
-
- private static final String TAG = "QSMediaPlayer";
-
- // Button IDs for QS controls
- static final int[] QS_ACTION_IDS = {
- R.id.action0,
- R.id.action1,
- R.id.action2,
- R.id.action3,
- R.id.action4
- };
-
- private final QSPanel mParent;
- private final Executor mForegroundExecutor;
- private final DelayableExecutor mBackgroundExecutor;
- private final SeekBarViewModel mSeekBarViewModel;
- private final SeekBarObserver mSeekBarObserver;
- private String mPackageName;
-
- /**
- * Initialize quick shade version of player
- * @param context
- * @param parent
- * @param routeManager Provides information about device
- * @param foregroundExecutor
- * @param backgroundExecutor
- * @param activityStarter
- */
- public QSMediaPlayer(Context context, ViewGroup parent, LocalMediaManager routeManager,
- Executor foregroundExecutor, DelayableExecutor backgroundExecutor,
- ActivityStarter activityStarter) {
- super(context, parent, routeManager, R.layout.qs_media_panel, QS_ACTION_IDS,
- foregroundExecutor, backgroundExecutor, activityStarter);
- mParent = (QSPanel) parent;
- mForegroundExecutor = foregroundExecutor;
- mBackgroundExecutor = backgroundExecutor;
- mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
- mSeekBarObserver = new SeekBarObserver(getView());
- // Can't use the viewAttachLifecycle of media player because remove/add is used to adjust
- // priority of players. As soon as it is removed, the lifecycle will end and the seek bar
- // will stop updating. So, use the lifecycle of the parent instead.
- mSeekBarViewModel.getProgress().observe(viewAttachLifecycle(parent), mSeekBarObserver);
- SeekBar bar = getView().findViewById(R.id.media_progress_bar);
- bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener());
- bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener());
- }
-
- /**
- * Add a media panel view based on a media description. Used for resumption
- * @param description
- * @param iconColor
- * @param bgColor
- * @param contentIntent
- * @param pkgName
- */
- public void setMediaSession(MediaSession.Token token, MediaDescription description,
- int iconColor, int bgColor, PendingIntent contentIntent, String pkgName) {
- mPackageName = pkgName;
- PackageManager pm = getContext().getPackageManager();
- Drawable icon = null;
- CharSequence appName = pkgName.substring(pkgName.lastIndexOf("."));
- try {
- icon = pm.getApplicationIcon(pkgName);
- appName = pm.getApplicationLabel(pm.getApplicationInfo(pkgName, 0));
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Error getting package information", e);
- }
-
- // Set what we can normally
- super.setMediaSession(token, icon, null, iconColor, bgColor, contentIntent,
- appName.toString(), null);
-
- // Then add info from MediaDescription
- ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
- if (albumView != null) {
- // Resize art in a background thread
- mBackgroundExecutor.execute(() -> processAlbumArt(description, albumView));
- }
-
- // Song name
- TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
- CharSequence songName = description.getTitle();
- titleText.setText(songName);
- titleText.setTextColor(iconColor);
-
- // Artist name (not in mini player)
- TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
- if (artistText != null) {
- CharSequence artistName = description.getSubtitle();
- artistText.setText(artistName);
- artistText.setTextColor(iconColor);
- }
-
- initLongPressMenu(iconColor);
-
- // Set buttons to resume state
- resetButtons();
- }
-
- /**
- * Update media panel view for the given media session
- * @param token token for this media session
- * @param icon app notification icon
- * @param largeIcon notification's largeIcon, used as a fallback for album art
- * @param iconColor foreground color (for text, icons)
- * @param bgColor background color
- * @param actionsContainer a LinearLayout containing the media action buttons
- * @param contentIntent Intent to send when user taps on player
- * @param appName Application title
- * @param key original notification's key
- */
- public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
- int iconColor, int bgColor, View actionsContainer, PendingIntent contentIntent,
- String appName, String key) {
-
- super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, appName,
- key);
-
- // Media controls
- if (actionsContainer != null) {
- LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
- int i = 0;
- for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
- final ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
- ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
- if (thatBtn == null || thatBtn.getDrawable() == null
- || thatBtn.getVisibility() != View.VISIBLE) {
- thisBtn.setVisibility(View.GONE);
- continue;
- }
-
- if (mMediaNotifView.getBackground() instanceof IlluminationDrawable) {
- ((IlluminationDrawable) mMediaNotifView.getBackground())
- .setupTouch(thisBtn, mMediaNotifView);
- }
-
- Drawable thatIcon = thatBtn.getDrawable();
- thisBtn.setImageDrawable(thatIcon.mutate());
- thisBtn.setVisibility(View.VISIBLE);
- thisBtn.setOnClickListener(v -> {
- Log.d(TAG, "clicking on other button");
- thatBtn.performClick();
- });
- }
-
- // Hide any unused buttons
- for (; i < QS_ACTION_IDS.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
- thisBtn.setVisibility(View.GONE);
- }
- }
-
- // Seek Bar
- final MediaController controller = new MediaController(getContext(), token);
- mBackgroundExecutor.execute(
- () -> mSeekBarViewModel.updateController(controller, iconColor));
-
- initLongPressMenu(iconColor);
- }
-
- private void initLongPressMenu(int iconColor) {
- // Set up long press menu
- View guts = mMediaNotifView.findViewById(R.id.media_guts);
- View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
- options.setMinimumHeight(guts.getHeight());
-
- View clearView = options.findViewById(R.id.remove);
- clearView.setOnClickListener(b -> {
- removePlayer();
- });
- ImageView removeIcon = options.findViewById(R.id.remove_icon);
- removeIcon.setImageTintList(ColorStateList.valueOf(iconColor));
- TextView removeText = options.findViewById(R.id.remove_text);
- removeText.setTextColor(iconColor);
-
- TextView cancelView = options.findViewById(R.id.cancel);
- cancelView.setTextColor(iconColor);
- cancelView.setOnClickListener(b -> {
- options.setVisibility(View.GONE);
- guts.setVisibility(View.VISIBLE);
- });
- // ... but don't enable it yet, and make sure is reset when the session is updated
- mMediaNotifView.setOnLongClickListener(null);
- options.setVisibility(View.GONE);
- guts.setVisibility(View.VISIBLE);
- }
-
- @Override
- protected void resetButtons() {
- super.resetButtons();
- mSeekBarViewModel.clearController();
- View guts = mMediaNotifView.findViewById(R.id.media_guts);
- View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
-
- mMediaNotifView.setOnLongClickListener(v -> {
- // Replace player view with close/cancel view
- guts.setVisibility(View.GONE);
- options.setVisibility(View.VISIBLE);
- return true; // consumed click
- });
- }
-
- /**
- * Sets the listening state of the player.
- *
- * Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid
- * unnecessary work when the QS panel is closed.
- *
- * @param listening True when player should be active. Otherwise, false.
- */
- public void setListening(boolean listening) {
- mSeekBarViewModel.setListening(listening);
- }
-
- @Override
- public void removePlayer() {
- Log.d(TAG, "removing player from parent: " + mParent);
- // Ensure this happens on the main thread (could happen in QSMediaBrowser callback)
- mForegroundExecutor.execute(() -> mParent.removeMediaPlayer(QSMediaPlayer.this));
- }
-
- @Override
- public String getMediaPlayerPackage() {
- if (getController() == null) {
- return mPackageName;
- }
- return super.getMediaPlayerPackage();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 0ac4d15..009ebd7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -32,23 +32,19 @@
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
import android.media.MediaDescription;
-import android.media.session.MediaSession;
import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.UserHandle;
import android.os.UserManager;
-import android.service.notification.StatusBarNotification;
import android.service.quicksettings.Tile;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import com.android.internal.logging.MetricsLogger;
@@ -56,17 +52,14 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.settingslib.Utils;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.media.InfoMediaManager;
-import com.android.settingslib.media.LocalMediaManager;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaControlPanel;
+import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile;
@@ -77,20 +70,16 @@
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.BrightnessController;
import com.android.systemui.settings.ToggleSliderView;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.concurrency.DelayableExecutor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -108,21 +97,13 @@
protected final Context mContext;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
private final BroadcastDispatcher mBroadcastDispatcher;
+ protected final MediaHost mMediaHost;
private String mCachedSpecs = "";
protected final View mBrightnessView;
private final H mHandler = new H();
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private final QSTileRevealController mQsTileRevealController;
- private final LinearLayout mMediaCarousel;
- private final ArrayList<QSMediaPlayer> mMediaPlayers = new ArrayList<>();
- private final LocalBluetoothManager mLocalBluetoothManager;
- private final Executor mForegroundExecutor;
- private final DelayableExecutor mBackgroundExecutor;
- private boolean mUpdateCarousel = false;
- private ActivityStarter mActivityStarter;
- private NotificationEntryManager mNotificationEntryManager;
-
protected boolean mExpanded;
protected boolean mListening;
@@ -158,15 +139,6 @@
}
};
- private final NotificationEntryListener mNotificationEntryListener =
- new NotificationEntryListener() {
- @Override
- public void onEntryRemoved(NotificationEntry entry, NotificationVisibility visibility,
- boolean removedByUser, int reason) {
- checkToRemoveMediaNotification(entry);
- }
- };
-
@Inject
public QSPanel(
@Named(VIEW_CONTEXT) Context context,
@@ -174,23 +146,15 @@
DumpManager dumpManager,
BroadcastDispatcher broadcastDispatcher,
QSLogger qsLogger,
- @Main Executor foregroundExecutor,
- @Background DelayableExecutor backgroundExecutor,
- @Nullable LocalBluetoothManager localBluetoothManager,
- ActivityStarter activityStarter,
- NotificationEntryManager entryManager,
+ MediaHost mediaHost,
UiEventLogger uiEventLogger
) {
super(context, attrs);
+ mMediaHost = mediaHost;
mContext = context;
mQSLogger = qsLogger;
mDumpManager = dumpManager;
- mForegroundExecutor = foregroundExecutor;
- mBackgroundExecutor = backgroundExecutor;
- mLocalBluetoothManager = localBluetoothManager;
mBroadcastDispatcher = broadcastDispatcher;
- mActivityStarter = activityStarter;
- mNotificationEntryManager = entryManager;
mUiEventLogger = uiEventLogger;
setOrientation(VERTICAL);
@@ -210,16 +174,6 @@
addDivider();
- // Add media carousel
- if (useQsMediaPlayer(context)) {
- HorizontalScrollView mediaScrollView = (HorizontalScrollView) LayoutInflater.from(
- mContext).inflate(R.layout.media_carousel, this, false);
- mMediaCarousel = mediaScrollView.findViewById(R.id.media_carousel);
- addView(mediaScrollView, 0);
- } else {
- mMediaCarousel = null;
- }
-
mFooter = new QSSecurityFooter(this, context);
addView(mFooter.getView());
@@ -230,145 +184,39 @@
}
@Override
- public void onVisibilityAggregated(boolean isVisible) {
- super.onVisibilityAggregated(isVisible);
- if (!isVisible && mUpdateCarousel) {
- for (QSMediaPlayer player : mMediaPlayers) {
- if (player.isPlaying()) {
- LayoutParams lp = (LayoutParams) player.getView().getLayoutParams();
- mMediaCarousel.removeView(player.getView());
- mMediaCarousel.addView(player.getView(), 0, lp);
- ((HorizontalScrollView) mMediaCarousel.getParent()).fullScroll(View.FOCUS_LEFT);
- mUpdateCarousel = false;
- break;
- }
- }
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ // Add media carousel at the end
+ if (useQsMediaPlayer(getContext())) {
+ addMediaHostView();
}
}
- /**
- * Add or update a player for the associated media session
- * @param token
- * @param icon
- * @param largeIcon
- * @param iconColor
- * @param bgColor
- * @param actionsContainer
- * @param notif
- * @param key
- */
- public void addMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
- int iconColor, int bgColor, View actionsContainer, StatusBarNotification notif,
- String key) {
- if (!useQsMediaPlayer(mContext)) {
- // Shouldn't happen, but just in case
- Log.e(TAG, "Tried to add media session without player!");
- return;
- }
- if (token == null) {
- Log.e(TAG, "Media session token was null!");
- return;
- }
-
- String packageName = notif.getPackageName();
- QSMediaPlayer player = findMediaPlayer(packageName, token, key);
-
- int playerWidth = (int) getResources().getDimension(R.dimen.qs_media_width);
- int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
- LayoutParams lp = new LayoutParams(playerWidth, ViewGroup.LayoutParams.MATCH_PARENT);
- lp.setMarginStart(padding);
- lp.setMarginEnd(padding);
-
- if (player == null) {
- Log.d(TAG, "creating new player for " + packageName);
- // Set up listener for device changes
- // TODO: integrate with MediaTransferManager?
- InfoMediaManager imm = new InfoMediaManager(mContext, notif.getPackageName(),
- notif.getNotification(), mLocalBluetoothManager);
- LocalMediaManager routeManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
- imm, notif.getPackageName());
-
- player = new QSMediaPlayer(mContext, this, routeManager, mForegroundExecutor,
- mBackgroundExecutor, mActivityStarter);
- player.setListening(mListening);
- if (player.isPlaying()) {
- mMediaCarousel.addView(player.getView(), 0, lp); // add in front
- } else {
- mMediaCarousel.addView(player.getView(), lp); // add at end
- }
- mMediaPlayers.add(player);
- } else if (player.isPlaying()) {
- mUpdateCarousel = true;
- }
-
- Log.d(TAG, "setting player session");
- String appName = Notification.Builder.recoverBuilder(getContext(), notif.getNotification())
- .loadHeaderAppName();
- player.setMediaSession(token, icon, largeIcon, iconColor, bgColor, actionsContainer,
- notif.getNotification().contentIntent, appName, key);
-
- if (mMediaPlayers.size() > 0) {
- ((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE);
- }
- }
-
- /**
- * Check for an existing media player using the given information
- * @param packageName
- * @param token
- * @param key
- * @return a player, or null if no match found
- */
- private QSMediaPlayer findMediaPlayer(String packageName, MediaSession.Token token,
- String key) {
- for (QSMediaPlayer player : mMediaPlayers) {
- if (player.getKey() == null || key == null) {
- // No notification key = loaded via mediabrowser, so just match on package
- if (packageName.equals(player.getMediaPlayerPackage())) {
- Log.d(TAG, "Found matching resume player by package: " + packageName);
- return player;
- }
- } else if (player.getMediaSessionToken().equals(token)) {
- Log.d(TAG, "Found matching player by token " + packageName);
- return player;
- } else if (packageName.equals(player.getMediaPlayerPackage())
- && key.equals(player.getKey())) {
- // Also match if it's the same package and notification key
- Log.d(TAG, "Found matching player by package " + packageName + ", " + key);
- return player;
- }
- }
- return null;
- }
-
- protected View getMediaPanel() {
- return mMediaCarousel;
- }
-
- /**
- * Remove the media player from the carousel
- * @param player Player to remove
- * @return true if removed, false if player was not found
- */
- protected boolean removeMediaPlayer(QSMediaPlayer player) {
- // Remove from list
- if (!mMediaPlayers.remove(player)) {
- return false;
- }
-
- // Check if we need to collapse the carousel now
- mMediaCarousel.removeView(player.getView());
- if (mMediaPlayers.size() == 0) {
- ((View) mMediaCarousel.getParent()).setVisibility(View.GONE);
- }
- return true;
+ protected void addMediaHostView() {
+ mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
+ mMediaHost.setExpansion(1.0f);
+ mMediaHost.setShowsOnlyActiveMedia(false);
+ ViewGroup hostView = mMediaHost.getHostView();
+ addView(hostView);
+ int sidePaddings = getResources().getDimensionPixelSize(
+ R.dimen.quick_settings_side_margins);
+ int bottomPadding = getResources().getDimensionPixelSize(
+ R.dimen.quick_settings_expanded_bottom_margin);
+ MarginLayoutParams layoutParams = (MarginLayoutParams) hostView.getLayoutParams();
+ layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ layoutParams.bottomMargin = bottomPadding;
+ hostView.setLayoutParams(layoutParams);
+ hostView.setPadding(sidePaddings, hostView.getPaddingTop(), sidePaddings,
+ hostView.getPaddingBottom());
}
private final QSMediaBrowser.Callback mMediaBrowserCallback = new QSMediaBrowser.Callback() {
@Override
public void addTrack(MediaDescription desc, ComponentName component,
QSMediaBrowser browser) {
- if (component == null) {
+ // TODO: Fix Resumption b/156104922
+/* if (component == null) {
Log.e(TAG, "Component cannot be null");
return;
}
@@ -404,7 +252,7 @@
int iconColor = Color.DKGRAY;
int bgColor = Color.LTGRAY;
player.setMediaSession(token, desc, iconColor, bgColor, browser.getAppIntent(),
- pkgName);
+ pkgName);*/
}
};
@@ -441,27 +289,6 @@
mHasLoadedMediaControls = true;
}
- private void checkToRemoveMediaNotification(NotificationEntry entry) {
- if (!useQsMediaPlayer(mContext)) {
- return;
- }
-
- if (!entry.isMediaNotification()) {
- return;
- }
-
- // If this entry corresponds to an existing set of controls, clear the controls
- // This will handle apps that use an action to clear their notification
- for (QSMediaPlayer p : mMediaPlayers) {
- if (p.getKey() != null && p.getKey().equals(entry.getKey())) {
- Log.d(TAG, "Clearing controls since notification removed " + entry.getKey());
- p.clearControls();
- return;
- }
- }
- Log.d(TAG, "Media notification removed but no player found " + entry.getKey());
- }
-
protected void addDivider() {
mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false);
mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(),
@@ -482,7 +309,11 @@
int numChildren = getChildCount();
for (int i = 0; i < numChildren; i++) {
View child = getChildAt(i);
- if (child.getVisibility() != View.GONE) height += child.getMeasuredHeight();
+ if (child.getVisibility() != View.GONE) {
+ height += child.getMeasuredHeight();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
+ height += layoutParams.topMargin + layoutParams.bottomMargin;
+ }
}
setMeasuredDimension(getMeasuredWidth(), height);
}
@@ -528,7 +359,6 @@
loadMediaResumptionControls();
}
}
- mNotificationEntryManager.addNotificationEntryListener(mNotificationEntryListener);
}
@Override
@@ -545,7 +375,6 @@
}
mDumpManager.unregisterDumpable(getDumpableTag());
mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
- mNotificationEntryManager.removeNotificationEntryListener(mNotificationEntryListener);
super.onDetachedFromWindow();
}
@@ -719,7 +548,6 @@
public void setListening(boolean listening) {
if (mListening == listening) return;
- mListening = listening;
if (mTileLayout != null) {
mQSLogger.logAllTilesChangeListening(listening, getDumpableTag(), mCachedSpecs);
mTileLayout.setListening(listening);
@@ -727,9 +555,6 @@
if (mListening) {
refreshAllTiles();
}
- for (QSMediaPlayer player : mMediaPlayers) {
- player.setListening(mListening);
- }
}
private String getTilesSpecs() {
@@ -1030,6 +855,10 @@
}
}
+ public MediaHost getMediaHost() {
+ return mMediaHost;
+ }
+
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
deleted file mode 100644
index 5cb75e6..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-import com.android.systemui.media.IlluminationDrawable;
-import com.android.systemui.media.MediaControlPanel;
-import com.android.systemui.plugins.ActivityStarter;
-
-import java.util.concurrent.Executor;
-
-/**
- * QQS mini media player
- */
-public class QuickQSMediaPlayer extends MediaControlPanel {
-
- private static final String TAG = "QQSMediaPlayer";
-
- // Button IDs for QS controls
- private static final int[] QQS_ACTION_IDS = {R.id.action0, R.id.action1, R.id.action2};
-
- /**
- * Initialize mini media player for QQS
- * @param context
- * @param parent
- * @param foregroundExecutor
- * @param backgroundExecutor
- * @param activityStarter
- */
- public QuickQSMediaPlayer(Context context, ViewGroup parent, Executor foregroundExecutor,
- Executor backgroundExecutor, ActivityStarter activityStarter) {
- super(context, parent, null, R.layout.qqs_media_panel, QQS_ACTION_IDS,
- foregroundExecutor, backgroundExecutor, activityStarter);
- }
-
- /**
- * Update media panel view for the given media session
- * @param token token for this media session
- * @param icon app notification icon
- * @param largeIcon notification's largeIcon, used as a fallback for album art
- * @param iconColor foreground color (for text, icons)
- * @param bgColor background color
- * @param actionsContainer a LinearLayout containing the media action buttons
- * @param actionsToShow indices of which actions to display in the mini player
- * (max 3: Notification.MediaStyle.MAX_MEDIA_BUTTONS_IN_COMPACT)
- * @param contentIntent Intent to send when user taps on the view
- * @param key original notification's key
- */
- public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
- int iconColor, int bgColor, View actionsContainer, int[] actionsToShow,
- PendingIntent contentIntent, String key) {
- // Only update if this is a different session and currently playing
- String oldPackage = "";
- if (getController() != null) {
- oldPackage = getController().getPackageName();
- }
- MediaController controller = new MediaController(getContext(), token);
- MediaSession.Token currentToken = getMediaSessionToken();
- boolean samePlayer = currentToken != null
- && currentToken.equals(token)
- && oldPackage.equals(controller.getPackageName());
- if (getController() != null && !samePlayer && !isPlaying(controller)) {
- return;
- }
-
- super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, null, key);
-
- LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
- int i = 0;
- if (actionsToShow != null) {
- int maxButtons = Math.min(actionsToShow.length, parentActionsLayout.getChildCount());
- maxButtons = Math.min(maxButtons, QQS_ACTION_IDS.length);
- for (; i < maxButtons; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(QQS_ACTION_IDS[i]);
- int thatId = NOTIF_ACTION_IDS[actionsToShow[i]];
- ImageButton thatBtn = parentActionsLayout.findViewById(thatId);
- if (thatBtn == null || thatBtn.getDrawable() == null
- || thatBtn.getVisibility() != View.VISIBLE) {
- thisBtn.setVisibility(View.GONE);
- continue;
- }
-
- if (mMediaNotifView.getBackground() instanceof IlluminationDrawable) {
- ((IlluminationDrawable) mMediaNotifView.getBackground())
- .setupTouch(thisBtn, mMediaNotifView);
- }
-
- Drawable thatIcon = thatBtn.getDrawable();
- thisBtn.setImageDrawable(thatIcon.mutate());
- thisBtn.setVisibility(View.VISIBLE);
- thisBtn.setOnClickListener(v -> {
- thatBtn.performClick();
- });
- }
- }
-
- // Hide any unused buttons
- for (; i < QQS_ACTION_IDS.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(QQS_ACTION_IDS[i]);
- thisBtn.setVisibility(View.GONE);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 6683a1c..dfd385d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -18,38 +18,34 @@
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.android.internal.logging.UiEventLogger;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.Utils;
-import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -67,16 +63,17 @@
private boolean mDisabledByPolicy;
private int mMaxTiles;
protected QSPanel mFullPanel;
- private QuickQSMediaPlayer mMediaPlayer;
/** Whether or not the QS media player feature is enabled. */
private boolean mUsingMediaPlayer;
/** Whether or not the QuickQSPanel currently contains a media player. */
- private boolean mHasMediaPlayer;
+ private boolean mShowHorizontalTileLayout;
private LinearLayout mHorizontalLinearLayout;
// Only used with media
- private QSTileLayout mMediaTileLayout;
+ private QSTileLayout mHorizontalTileLayout;
private QSTileLayout mRegularTileLayout;
+ private int mLastOrientation = -1;
+ private int mMediaBottomMargin;
@Inject
public QuickQSPanel(
@@ -85,16 +82,11 @@
DumpManager dumpManager,
BroadcastDispatcher broadcastDispatcher,
QSLogger qsLogger,
- @Main Executor foregroundExecutor,
- @Background DelayableExecutor backgroundExecutor,
- @Nullable LocalBluetoothManager localBluetoothManager,
- ActivityStarter activityStarter,
- NotificationEntryManager entryManager,
+ MediaHost mediaHost,
UiEventLogger uiEventLogger
) {
- super(context, attrs, dumpManager, broadcastDispatcher, qsLogger,
- foregroundExecutor, backgroundExecutor, localBluetoothManager, activityStarter,
- entryManager, uiEventLogger);
+ super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost,
+ uiEventLogger);
if (mFooter != null) {
removeView(mFooter.getView());
}
@@ -104,6 +96,8 @@
}
removeView((View) mTileLayout);
}
+ mMediaBottomMargin = getResources().getDimensionPixelSize(
+ R.dimen.quick_settings_media_extra_bottom_margin);
mUsingMediaPlayer = Utils.useQsMediaPlayer(context);
if (mUsingMediaPlayer) {
@@ -112,40 +106,95 @@
mHorizontalLinearLayout.setClipChildren(false);
mHorizontalLinearLayout.setClipToPadding(false);
- int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing);
- mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout,
- foregroundExecutor, backgroundExecutor, activityStarter);
- LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
- lp2.setMarginEnd(marginSize);
- lp2.setMarginStart(0);
- mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2);
-
- mTileLayout = new DoubleLineTileLayout(context, mUiEventLogger);
- mMediaTileLayout = mTileLayout;
+ DoubleLineTileLayout horizontalTileLayout = new DoubleLineTileLayout(context,
+ mUiEventLogger);
+ horizontalTileLayout.setPaddingRelative(
+ horizontalTileLayout.getPaddingStart(),
+ horizontalTileLayout.getPaddingTop(),
+ horizontalTileLayout.getPaddingEnd(),
+ mContext.getResources().getDimensionPixelSize(
+ R.dimen.qqs_horizonal_tile_padding_bottom));
+ mHorizontalTileLayout = horizontalTileLayout;
mRegularTileLayout = new HeaderTileLayout(context, mUiEventLogger);
- LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
- lp.setMarginEnd(0);
- lp.setMarginStart(marginSize);
- mHorizontalLinearLayout.addView((View) mTileLayout, lp);
+ LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
+ int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing);
+ lp.setMarginStart(0);
+ lp.setMarginEnd(marginSize);
+ lp.gravity = Gravity.CENTER_VERTICAL;
+ mHorizontalLinearLayout.addView((View) mHorizontalTileLayout, lp);
sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
+ boolean useHorizontal = shouldUseHorizontalTileLayout();
+ mTileLayout = useHorizontal ? mHorizontalTileLayout : mRegularTileLayout;
mTileLayout.setListening(mListening);
addView(mHorizontalLinearLayout, 0 /* Between brightness and footer */);
- ((View) mRegularTileLayout).setVisibility(View.GONE);
+ ((View) mRegularTileLayout).setVisibility(!useHorizontal ? View.VISIBLE : View.GONE);
+ mHorizontalLinearLayout.setVisibility(useHorizontal ? View.VISIBLE : View.GONE);
addView((View) mRegularTileLayout, 0);
super.setPadding(0, 0, 0, 0);
+ applySideMargins(mHorizontalLinearLayout);
+ applyBottomMargin((View) mRegularTileLayout);
} else {
sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
mTileLayout = new HeaderTileLayout(context, mUiEventLogger);
mTileLayout.setListening(mListening);
addView((View) mTileLayout, 0 /* Between brightness and footer */);
super.setPadding(0, 0, 0, 0);
+ applyBottomMargin((View) mTileLayout);
}
}
- public QuickQSMediaPlayer getMediaPlayer() {
- return mMediaPlayer;
+ private void applyBottomMargin(View view) {
+ int margin = getResources().getDimensionPixelSize(R.dimen.qs_header_tile_margin_bottom);
+ MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
+ layoutParams.bottomMargin = margin;
+ view.setLayoutParams(layoutParams);
+ }
+
+ private void applySideMargins(View view) {
+ int margin = getResources().getDimensionPixelSize(R.dimen.qs_header_tile_margin_horizontal);
+ MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
+ layoutParams.setMarginStart(margin);
+ layoutParams.setMarginEnd(margin);
+ view.setLayoutParams(layoutParams);
+ }
+
+ private void reAttachMediaHost() {
+ if (mMediaHost == null) {
+ return;
+ }
+ boolean horizontal = shouldUseHorizontalTileLayout();
+ ViewGroup host = mMediaHost.getHostView();
+ ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this;
+ ViewGroup currentParent = (ViewGroup) host.getParent();
+ if (currentParent != newParent) {
+ if (currentParent != null) {
+ currentParent.removeView(host);
+ }
+ newParent.addView(host);
+ LinearLayout.LayoutParams layoutParams = (LayoutParams) host.getLayoutParams();
+ layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ layoutParams.width = horizontal ? 0 : ViewGroup.LayoutParams.MATCH_PARENT;
+ layoutParams.weight = horizontal ? 1.5f : 0;
+ layoutParams.bottomMargin = mMediaBottomMargin;
+ int marginStart = horizontal
+ ? getResources().getDimensionPixelSize(R.dimen.qs_header_tile_margin_horizontal)
+ : 0;
+ layoutParams.setMarginStart(marginStart);
+ }
+ }
+
+ @Override
+ protected void addMediaHostView() {
+ mMediaHost.setVisibleChangedListener((visible) -> {
+ switchTileLayout();
+ return null;
+ });
+ mMediaHost.init(MediaHierarchyManager.LOCATION_QQS);
+ mMediaHost.setExpansion(0.0f);
+ mMediaHost.setShowsOnlyActiveMedia(true);
+ reAttachMediaHost();
}
@Override
@@ -191,10 +240,19 @@
super.drawTile(r, state);
}
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (newConfig.orientation != mLastOrientation) {
+ mLastOrientation = newConfig.orientation;
+ switchTileLayout();
+ }
+ }
+
boolean switchTileLayout() {
if (!mUsingMediaPlayer) return false;
- mHasMediaPlayer = mMediaPlayer.hasMediaSession();
- if (mHasMediaPlayer && mHorizontalLinearLayout.getVisibility() == View.GONE) {
+ mShowHorizontalTileLayout = shouldUseHorizontalTileLayout();
+ if (mShowHorizontalTileLayout && mHorizontalLinearLayout.getVisibility() == View.GONE) {
mHorizontalLinearLayout.setVisibility(View.VISIBLE);
((View) mRegularTileLayout).setVisibility(View.GONE);
mTileLayout.setListening(false);
@@ -202,11 +260,13 @@
mTileLayout.removeTile(record);
record.tile.removeCallback(record.callback);
}
- mTileLayout = mMediaTileLayout;
+ mTileLayout = mHorizontalTileLayout;
if (mHost != null) setTiles(mHost.getTiles());
mTileLayout.setListening(mListening);
+ reAttachMediaHost();
return true;
- } else if (!mHasMediaPlayer && mHorizontalLinearLayout.getVisibility() == View.VISIBLE) {
+ } else if (!mShowHorizontalTileLayout
+ && mHorizontalLinearLayout.getVisibility() == View.VISIBLE) {
mHorizontalLinearLayout.setVisibility(View.GONE);
((View) mRegularTileLayout).setVisibility(View.VISIBLE);
mTileLayout.setListening(false);
@@ -217,14 +277,21 @@
mTileLayout = mRegularTileLayout;
if (mHost != null) setTiles(mHost.getTiles());
mTileLayout.setListening(mListening);
+ reAttachMediaHost();
return true;
}
return false;
}
- /** Returns true if this panel currently contains a media player. */
- public boolean hasMediaPlayer() {
- return mHasMediaPlayer;
+ private boolean shouldUseHorizontalTileLayout() {
+ return mMediaHost.getVisible()
+ && getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+ }
+
+ /** Returns true if this panel currently uses a horizontal tile layout. */
+ public boolean usesHorizontalLayout() {
+ return mShowHorizontalTileLayout;
}
@Override
@@ -341,7 +408,7 @@
setClipChildren(false);
setClipToPadding(false);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT);
+ LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER_HORIZONTAL;
setLayoutParams(lp);
}
@@ -354,6 +421,7 @@
@Override
public void onFinishInflate(){
+ super.onFinishInflate();
updateResources();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index b15c6a3..3b2bea8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -220,6 +220,10 @@
mNextAlarmTextView.setSelected(true);
}
+ public QuickQSPanel getHeaderQsPanel() {
+ return mHeaderQsPanel;
+ }
+
private List<String> getIgnoredIconSlots() {
ArrayList<String> ignored = new ArrayList<>();
ignored.add(mContext.getResources().getString(
@@ -336,23 +340,6 @@
com.android.internal.R.dimen.quick_qs_offset_height);
mSystemIconsView.setLayoutParams(mSystemIconsView.getLayoutParams());
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
-
- if (mQsDisabled) {
- lp.height = resources.getDimensionPixelSize(
- com.android.internal.R.dimen.quick_qs_offset_height);
- } else if (useQsMediaPlayer(mContext) && mHeaderQsPanel.hasMediaPlayer()) {
- lp.height = Math.max(getMinimumHeight(),
- resources.getDimensionPixelSize(
- com.android.internal.R.dimen.quick_qs_total_height_with_media));
- } else {
- lp.height = Math.max(getMinimumHeight(),
- resources.getDimensionPixelSize(
- com.android.internal.R.dimen.quick_qs_total_height));
- }
-
- setLayoutParams(lp);
-
updateStatusIconAlphaAnimator();
updateHeaderTextContainerAlphaAnimator();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index 6249f82..aa63b40 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -106,6 +106,7 @@
}
public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
mName.setEnabled(enabled);
mAvatar.setEnabled(enabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index c2473280..abd7e71 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -72,6 +72,7 @@
window.getDecorView();
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
window.setGravity(Gravity.TOP);
+ setTitle(R.string.screenrecord_name);
setContentView(R.layout.screen_record_dialog);
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index e67b3d7..02a7aca 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -813,4 +813,12 @@
updateVisibility(true /* visible */);
}
}
+
+ /** @return the container token for the secondary split root task. */
+ public WindowContainerToken getSecondaryRoot() {
+ if (mSplits == null || mSplits.mSecondary == null) {
+ return null;
+ }
+ return mSplits.mSecondary.token;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index e32d174..9f4932e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -44,14 +44,17 @@
import android.view.View;
import android.widget.ImageView;
+import androidx.annotation.NonNull;
+
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.keyguard.KeyguardMediaPlayer;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.media.MediaData;
+import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -113,7 +116,6 @@
private ScrimController mScrimController;
@Nullable
private LockscreenWallpaper mLockscreenWallpaper;
- private final KeyguardMediaPlayer mMediaPlayer;
private final Executor mMainExecutor;
@@ -187,13 +189,12 @@
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
- KeyguardMediaPlayer keyguardMediaPlayer,
@Main Executor mainExecutor,
- DeviceConfigProxy deviceConfig) {
+ DeviceConfigProxy deviceConfig,
+ MediaDataManager mediaDataManager) {
mContext = context;
mMediaArtworkProcessor = mediaArtworkProcessor;
mKeyguardBypassController = keyguardBypassController;
- mMediaPlayer = keyguardMediaPlayer;
mMediaListeners = new ArrayList<>();
// TODO: use MediaSessionManager.SessionListener to hook us up to future updates
// in session state
@@ -204,14 +205,26 @@
mNotificationShadeWindowController = notificationShadeWindowController;
mEntryManager = notificationEntryManager;
mMainExecutor = mainExecutor;
+
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+
@Override
public void onPendingEntryAdded(NotificationEntry entry) {
- findAndUpdateMediaNotifications();
+ mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
}
@Override
public void onPreEntryUpdated(NotificationEntry entry) {
+ mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
+ }
+
+ @Override
+ public void onEntryInflated(NotificationEntry entry) {
+ findAndUpdateMediaNotifications();
+ }
+
+ @Override
+ public void onEntryReinflated(NotificationEntry entry) {
findAndUpdateMediaNotifications();
}
@@ -222,6 +235,7 @@
boolean removedByUser,
int reason) {
onNotificationRemoved(entry.getKey());
+ mediaDataManager.onNotificationRemoved(entry.getKey());
}
});
@@ -278,7 +292,7 @@
public void addCallback(MediaListener callback) {
mMediaListeners.add(callback);
- callback.onMetadataOrStateChanged(mMediaMetadata,
+ callback.onPrimaryMetadataOrStateChanged(mMediaMetadata,
getMediaControllerPlaybackState(mMediaController));
}
@@ -392,7 +406,7 @@
@PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController);
ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners);
for (int i = 0; i < callbacks.size(); i++) {
- callbacks.get(i).onMetadataOrStateChanged(mMediaMetadata, state);
+ callbacks.get(i).onPrimaryMetadataOrStateChanged(mMediaMetadata, state);
}
}
@@ -473,7 +487,6 @@
&& mBiometricUnlockController.isWakeAndUnlock();
if (mKeyguardStateController.isLaunchTransitionFadingAway() || wakeAndUnlock) {
mBackdrop.setVisibility(View.INVISIBLE);
- mMediaPlayer.clearControls();
Trace.endSection();
return;
}
@@ -496,14 +509,6 @@
}
}
- NotificationEntry entry = mEntryManager
- .getActiveNotificationUnfiltered(mMediaNotificationKey);
- if (entry != null) {
- mMediaPlayer.updateControls(entry, getMediaIcon(), mediaMetadata);
- } else {
- mMediaPlayer.clearControls();
- }
-
// Process artwork on a background thread and send the resulting bitmap to
// finishUpdateMediaMetaData.
if (metaDataChanged) {
@@ -626,7 +631,6 @@
// We are unlocking directly - no animation!
mBackdrop.setVisibility(View.GONE);
mBackdropBack.setImageDrawable(null);
- mMediaPlayer.clearControls();
if (windowController != null) {
windowController.setBackdropShowing(false);
}
@@ -643,7 +647,6 @@
mBackdrop.setVisibility(View.GONE);
mBackdropFront.animate().cancel();
mBackdropBack.setImageDrawable(null);
- mMediaPlayer.clearControls();
mMainExecutor.execute(mHideBackdropFront);
});
if (mKeyguardStateController.isKeyguardFadingAway()) {
@@ -750,6 +753,7 @@
* @param state Current playback state
* @see PlaybackState.State
*/
- void onMetadataOrStateChanged(MediaMetadata metadata, @PlaybackState.State int state);
+ default void onPrimaryMetadataOrStateChanged(MediaMetadata metadata,
+ @PlaybackState.State int state) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 3cb2a2a..1079f10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -52,7 +52,7 @@
/**
* Updates the visual representation of the notifications.
*/
- void updateNotificationViews();
+ void updateNotificationViews(String reason);
/**
* Returns the maximum number of notifications to show while locked.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 02c98ae..88f148b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -121,14 +121,14 @@
}
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
- return canHandleMotionEvent() && startExpansion(event)
+ return maybeStartExpansion(event)
}
- private fun canHandleMotionEvent(): Boolean {
- return !wakeUpCoordinator.canShowPulsingHuns || qsExpanded || bouncerShowing
- }
-
- private fun startExpansion(event: MotionEvent): Boolean {
+ private fun maybeStartExpansion(event: MotionEvent): Boolean {
+ if (!wakeUpCoordinator.canShowPulsingHuns || qsExpanded ||
+ bouncerShowing) {
+ return false
+ }
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain()
}
@@ -177,14 +177,9 @@
}
override fun onTouchEvent(event: MotionEvent): Boolean {
- if (!canHandleMotionEvent()) {
- return false
+ if (!isExpanding) {
+ return maybeStartExpansion(event)
}
-
- if (!isExpanding || event.actionMasked == MotionEvent.ACTION_DOWN) {
- return startExpansion(event)
- }
-
velocityTracker!!.addMovement(event)
val y = event.y
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index de7e36d9..f0fed13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -21,9 +21,9 @@
import android.os.Handler;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.KeyguardMediaPlayer;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.MediaArtworkProcessor;
@@ -95,9 +95,9 @@
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
- KeyguardMediaPlayer keyguardMediaPlayer,
@Main Executor mainExecutor,
- DeviceConfigProxy deviceConfigProxy) {
+ DeviceConfigProxy deviceConfigProxy,
+ MediaDataManager mediaDataManager) {
return new NotificationMediaManager(
context,
statusBarLazy,
@@ -105,9 +105,9 @@
notificationEntryManager,
mediaArtworkProcessor,
keyguardBypassController,
- keyguardMediaPlayer,
mainExecutor,
- deviceConfigProxy);
+ deviceConfigProxy,
+ mediaDataManager);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
index 75b41ca..eee9cc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.notification;
+import android.graphics.drawable.Drawable;
import android.util.FloatProperty;
+import android.util.Log;
import android.util.Property;
import android.view.View;
@@ -35,6 +37,100 @@
public static final AnimatableProperty Y = AnimatableProperty.from(View.Y,
R.id.y_animator_tag, R.id.y_animator_tag_start_value, R.id.y_animator_tag_end_value);
+ /**
+ * Similar to X, however this doesn't allow for any other modifications other than from this
+ * property. When using X, it's possible that the view is laid out during the animation,
+ * which could break the continuity
+ */
+ public static final AnimatableProperty ABSOLUTE_X = AnimatableProperty.from(
+ new FloatProperty<View>("ViewAbsoluteX") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setTag(R.id.absolute_x_current_value, value);
+ View.X.set(view, value);
+ }
+
+ @Override
+ public Float get(View view) {
+ Object tag = view.getTag(R.id.absolute_x_current_value);
+ if (tag instanceof Float) {
+ return (Float) tag;
+ }
+ return View.X.get(view);
+ }
+ },
+ R.id.absolute_x_animator_tag,
+ R.id.absolute_x_animator_start_tag,
+ R.id.absolute_x_animator_end_tag);
+
+ /**
+ * Similar to Y, however this doesn't allow for any other modifications other than from this
+ * property. When using X, it's possible that the view is laid out during the animation,
+ * which could break the continuity
+ */
+ public static final AnimatableProperty ABSOLUTE_Y = AnimatableProperty.from(
+ new FloatProperty<View>("ViewAbsoluteY") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setTag(R.id.absolute_y_current_value, value);
+ View.Y.set(view, value);
+ }
+
+ @Override
+ public Float get(View view) {
+ Object tag = view.getTag(R.id.absolute_y_current_value);
+ if (tag instanceof Float) {
+ return (Float) tag;
+ }
+ return View.Y.get(view);
+ }
+ },
+ R.id.absolute_y_animator_tag,
+ R.id.absolute_y_animator_start_tag,
+ R.id.absolute_y_animator_end_tag);
+
+ public static final AnimatableProperty WIDTH = AnimatableProperty.from(
+ new FloatProperty<View>("ViewWidth") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setTag(R.id.view_width_current_value, value);
+ view.setRight((int) (view.getLeft() + value));
+ }
+
+ @Override
+ public Float get(View view) {
+ Object tag = view.getTag(R.id.view_width_current_value);
+ if (tag instanceof Float) {
+ return (Float) tag;
+ }
+ return (float) view.getWidth();
+ }
+ },
+ R.id.view_width_animator_tag,
+ R.id.view_width_animator_start_tag,
+ R.id.view_width_animator_end_tag);
+
+ public static final AnimatableProperty HEIGHT = AnimatableProperty.from(
+ new FloatProperty<View>("ViewHeight") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setTag(R.id.view_height_current_value, value);
+ view.setBottom((int) (view.getTop() + value));
+ }
+
+ @Override
+ public Float get(View view) {
+ Object tag = view.getTag(R.id.view_height_current_value);
+ if (tag instanceof Float) {
+ return (Float) tag;
+ }
+ return (float) view.getHeight();
+ }
+ },
+ R.id.view_height_animator_tag,
+ R.id.view_height_animator_start_tag,
+ R.id.view_height_animator_end_tag);
+
public abstract int getAnimationStartTag();
public abstract int getAnimationEndTag();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index d251777..d647124 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -674,7 +674,7 @@
public void updateNotifications(String reason) {
reapplyFilterAndSort(reason);
if (mPresenter != null && !mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
- mPresenter.updateNotificationViews();
+ mPresenter.updateNotificationViews(reason);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 1f9d3af..b1b6a1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -34,13 +34,20 @@
*/
public class PropertyAnimator {
+ /**
+ * Set a property on a view, updating its value, even if it's already animating.
+ * The @param animated can be used to request an animation.
+ * If the view isn't animated, this utility will update the current animation if existent,
+ * such that the end value will point to @param newEndValue or apply it directly if there's
+ * no animation.
+ */
public static <T extends View> void setProperty(final T view,
AnimatableProperty animatableProperty, float newEndValue,
AnimationProperties properties, boolean animated) {
int animatorTag = animatableProperty.getAnimatorTag();
ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag);
if (previousAnimator != null || animated) {
- startAnimation(view, animatableProperty, newEndValue, properties);
+ startAnimation(view, animatableProperty, newEndValue, animated ? properties : null);
} else {
// no new animation needed, let's just apply the value
animatableProperty.getProperty().set(view, newEndValue);
@@ -60,8 +67,8 @@
}
int animatorTag = animatableProperty.getAnimatorTag();
ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag);
- AnimationFilter filter = properties.getAnimationFilter();
- if (!filter.shouldAnimateProperty(property)) {
+ AnimationFilter filter = properties != null ? properties.getAnimationFilter() : null;
+ if (filter == null || !filter.shouldAnimateProperty(property)) {
// just a local update was performed
if (previousAnimator != null) {
// we need to increase all animation keyframes of the previous animator by the
@@ -82,6 +89,14 @@
}
Float currentValue = property.get(view);
+ AnimatorListenerAdapter listener = properties.getAnimationFinishListener(property);
+ if (currentValue.equals(newEndValue)) {
+ // Skip the animation!
+ if (listener != null) {
+ listener.onAnimationEnd(null);
+ }
+ return;
+ }
ValueAnimator animator = ValueAnimator.ofFloat(currentValue, newEndValue);
animator.addUpdateListener(
animation -> property.set(view, (Float) animation.getAnimatedValue()));
@@ -96,7 +111,6 @@
|| previousAnimator.getAnimatedFraction() == 0)) {
animator.setStartDelay(properties.delay);
}
- AnimatorListenerAdapter listener = properties.getAnimationFinishListener(property);
if (listener != null) {
animator.addListener(listener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index cb0c283..634872d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -209,7 +209,7 @@
}
/** The key for this notification. Guaranteed to be immutable and unique */
- public String getKey() {
+ @NonNull public String getKey() {
return mKey;
}
@@ -217,7 +217,7 @@
* The StatusBarNotification that represents one half of a NotificationEntry (the other half
* being the Ranking). This object is swapped out whenever a notification is updated.
*/
- public StatusBarNotification getSbn() {
+ @NonNull public StatusBarNotification getSbn() {
return mSbn;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
index 59f119e..3fab6f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection.notifcollection;
+import android.annotation.NonNull;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
@@ -43,13 +44,13 @@
* there is no guarantee of order and they may not have had a chance to initialize yet. Instead,
* use {@link #onEntryAdded} which is called after all initialization.
*/
- default void onEntryInit(NotificationEntry entry) {
+ default void onEntryInit(@NonNull NotificationEntry entry) {
}
/**
* Called whenever a notification with a new key is posted.
*/
- default void onEntryAdded(NotificationEntry entry) {
+ default void onEntryAdded(@NonNull NotificationEntry entry) {
}
/**
@@ -64,7 +65,7 @@
* immediately after a user dismisses a notification: we wait until we receive confirmation from
* system server before considering the notification removed.
*/
- default void onEntryRemoved(NotificationEntry entry, @CancellationReason int reason) {
+ default void onEntryRemoved(@NonNull NotificationEntry entry, @CancellationReason int reason) {
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
index 88888d1..0fd865b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
@@ -75,7 +75,7 @@
mediaManager.addCallback(this)
}
- override fun onMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) {
+ override fun onPrimaryMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) {
val previous = currentMediaEntry
var newEntry = entryManager
.getActiveNotificationUnfiltered(mediaManager.mediaNotificationKey)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 5797944..0831c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -767,6 +767,10 @@
return mContentTranslation;
}
+ public boolean wantsAddAndRemoveAnimations() {
+ return true;
+ }
+
/**
* A listener notifying when {@link #getActualHeight} changes.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
index 0ccebc13..56f8e08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
@@ -109,7 +109,7 @@
}
@Nullable
- private CharSequence resolveText(Notification notification) {
+ public static CharSequence resolveText(Notification notification) {
CharSequence contentText = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
if (contentText == null) {
contentText = notification.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
@@ -118,7 +118,7 @@
}
@Nullable
- private CharSequence resolveTitle(Notification notification) {
+ public static CharSequence resolveTitle(Notification notification) {
CharSequence titleText = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
if (titleText == null) {
titleText = notification.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index c67c016..a9491c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -191,38 +191,6 @@
final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras
.getParcelable(Notification.EXTRA_MEDIA_SESSION);
- if (Utils.useQsMediaPlayer(mContext) && token != null) {
- final int[] compactActions = mRow.getEntry().getSbn().getNotification().extras
- .getIntArray(Notification.EXTRA_COMPACT_ACTIONS);
- int tintColor = getNotificationHeader().getOriginalIconColor();
- NotificationShadeWindowController ctrl = Dependency.get(
- NotificationShadeWindowController.class);
- QuickQSPanel panel = ctrl.getNotificationShadeView().findViewById(
- com.android.systemui.R.id.quick_qs_panel);
- StatusBarNotification sbn = mRow.getEntry().getSbn();
- Notification notif = sbn.getNotification();
- Drawable iconDrawable = notif.getSmallIcon().loadDrawable(mContext);
- panel.getMediaPlayer().setMediaSession(token,
- iconDrawable,
- notif.getLargeIcon(),
- tintColor,
- mBackgroundColor,
- mActions,
- compactActions,
- notif.contentIntent,
- sbn.getKey());
- QSPanel bigPanel = ctrl.getNotificationShadeView().findViewById(
- com.android.systemui.R.id.quick_settings_panel);
- bigPanel.addMediaSession(token,
- iconDrawable,
- notif.getLargeIcon(),
- tintColor,
- mBackgroundColor,
- mActions,
- sbn,
- sbn.getKey());
- }
-
boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar();
if (token == null || (COMPACT_MEDIA_TAG.equals(mView.getTag()) && !showCompactSeekbar)) {
if (mSeekBarView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java
index ab055e1..3ac322f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -37,7 +38,6 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mContentView = findViewById(R.id.keyguard_media_view);
}
@Override
@@ -52,4 +52,17 @@
public void setBackgroundColor(int color) {
setTintColor(color);
}
+
+ public void setContentView(ViewGroup contentView) {
+ mContentView = contentView;
+ addView(contentView);
+ ViewGroup.LayoutParams layoutParams = contentView.getLayoutParams();
+ layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ }
+
+ @Override
+ public boolean wantsAddAndRemoveAnimations() {
+ return false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
new file mode 100644
index 0000000..9cf1f74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationSectionLog
+import javax.inject.Inject
+import javax.inject.Singleton
+
+private const val TAG = "NotifSections"
+
+@Singleton
+class NotificationSectionsLogger @Inject constructor(
+ @NotificationSectionLog private val logBuffer: LogBuffer
+) {
+
+ fun logStartSectionUpdate(reason: String) = logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = reason },
+ { "Updating section boundaries: $reason" }
+ )
+
+ fun logStr(str: String) = logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = str },
+ { str1 ?: "" }
+ )
+
+ fun logPosition(position: Int, label: String) = logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = position
+ str1 = label
+ },
+ { "$int1: $str1" }
+ )
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index 6eec1ca..dcf30ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -29,10 +29,12 @@
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.keyguard.KeyguardMediaPlayer;
import com.android.systemui.R;
+import com.android.systemui.media.KeyguardMediaController;
+import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
@@ -74,17 +76,17 @@
private final StatusBarStateController mStatusBarStateController;
private final ConfigurationController mConfigurationController;
private final PeopleHubViewAdapter mPeopleHubViewAdapter;
- private final KeyguardMediaPlayer mKeyguardMediaPlayer;
private final NotificationSectionsFeatureManager mSectionsFeatureManager;
+ private final KeyguardMediaController mKeyguardMediaController;
private final int mNumberOfSections;
-
+ private final NotificationSectionsLogger mLogger;
private final PeopleHubViewBoundary mPeopleHubViewBoundary = new PeopleHubViewBoundary() {
@Override
public void setVisible(boolean isVisible) {
if (mPeopleHubVisible != isVisible) {
mPeopleHubVisible = isVisible;
if (mInitialized) {
- updateSectionBoundaries();
+ updateSectionBoundaries("PeopleHub visibility changed");
}
}
}
@@ -123,15 +125,18 @@
StatusBarStateController statusBarStateController,
ConfigurationController configurationController,
PeopleHubViewAdapter peopleHubViewAdapter,
- KeyguardMediaPlayer keyguardMediaPlayer,
- NotificationSectionsFeatureManager sectionsFeatureManager) {
+ KeyguardMediaController keyguardMediaController,
+ NotificationSectionsFeatureManager sectionsFeatureManager,
+ NotificationSectionsLogger logger) {
+
mActivityStarter = activityStarter;
mStatusBarStateController = statusBarStateController;
mConfigurationController = configurationController;
mPeopleHubViewAdapter = peopleHubViewAdapter;
- mKeyguardMediaPlayer = keyguardMediaPlayer;
mSectionsFeatureManager = sectionsFeatureManager;
mNumberOfSections = mSectionsFeatureManager.getNumberOfBuckets();
+ mKeyguardMediaController = keyguardMediaController;
+ mLogger = logger;
}
NotificationSection[] createSectionsForBuckets() {
@@ -205,12 +210,9 @@
mIncomingHeader.setHeaderText(R.string.notification_section_header_incoming);
mIncomingHeader.setOnHeaderClickListener(this::onGentleHeaderClick);
- if (mMediaControlsView != null) {
- mKeyguardMediaPlayer.unbindView();
- }
mMediaControlsView = reinflateView(mMediaControlsView, layoutInflater,
R.layout.keyguard_media_header);
- mKeyguardMediaPlayer.bindView(mMediaControlsView);
+ mKeyguardMediaController.attach(mMediaControlsView);
}
/** Listener for when the "clear all" button is clicked on the gentle notification header. */
@@ -250,15 +252,70 @@
return null;
}
+ private void logShadeContents() {
+ final int childCount = mParent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mParent.getChildAt(i);
+ if (child == mIncomingHeader) {
+ mLogger.logPosition(i, "INCOMING HEADER");
+ continue;
+ }
+ if (child == mMediaControlsView) {
+ mLogger.logPosition(i, "MEDIA CONTROLS");
+ continue;
+ }
+ if (child == mPeopleHubView) {
+ mLogger.logPosition(i, "CONVERSATIONS HEADER");
+ continue;
+ }
+ if (child == mAlertingHeader) {
+ mLogger.logPosition(i, "ALERTING HEADER");
+ continue;
+ }
+ if (child == mGentleHeader) {
+ mLogger.logPosition(i, "SILENT HEADER");
+ continue;
+ }
+
+ if (!(child instanceof ExpandableNotificationRow)) {
+ mLogger.logPosition(i, "other:" + child.getClass().getName());
+ continue;
+ }
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ // Once we enter a new section, calculate the target position for the header.
+ switch (row.getEntry().getBucket()) {
+ case BUCKET_HEADS_UP:
+ mLogger.logPosition(i, "Heads Up");
+ break;
+ case BUCKET_PEOPLE:
+ mLogger.logPosition(i, "Conversation");
+ break;
+ case BUCKET_ALERTING:
+ mLogger.logPosition(i, "Alerting");
+ break;
+ case BUCKET_SILENT:
+ mLogger.logPosition(i, "Silent");
+ break;
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void updateSectionBoundaries() {
+ updateSectionBoundaries("test");
+ }
+
/**
* Should be called whenever notifs are added, removed, or updated. Updates section boundary
* bookkeeping and adds/moves/removes section headers if appropriate.
*/
- void updateSectionBoundaries() {
+ void updateSectionBoundaries(String reason) {
if (!isUsingMultipleSections()) {
return;
}
+ mLogger.logStartSectionUpdate(reason);
+
// The overall strategy here is to iterate over the current children of mParent, looking
// for where the sections headers are currently positioned, and where each section begins.
// Then, once we find the start of a new section, we track that position as the "target" for
@@ -267,7 +324,6 @@
final boolean showHeaders = mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
final boolean usingPeopleFiltering = mSectionsFeatureManager.isFilteringEnabled();
- final boolean isKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
final boolean usingMediaControls = mSectionsFeatureManager.isMediaControlsEnabled();
boolean peopleNotifsPresent = false;
@@ -275,7 +331,7 @@
int currentMediaControlsIdx = -1;
// Currently, just putting media controls in the front and incrementing the position based
// on the number of heads-up notifs.
- int mediaControlsTarget = isKeyguard && usingMediaControls ? 0 : -1;
+ int mediaControlsTarget = usingMediaControls ? 0 : -1;
int currentIncomingHeaderIdx = -1;
int incomingHeaderTarget = -1;
int currentPeopleHeaderIdx = -1;
@@ -293,27 +349,33 @@
// Track the existing positions of the headers
if (child == mIncomingHeader) {
+ mLogger.logPosition(i, "INCOMING HEADER");
currentIncomingHeaderIdx = i;
continue;
}
if (child == mMediaControlsView) {
+ mLogger.logPosition(i, "MEDIA CONTROLS");
currentMediaControlsIdx = i;
continue;
}
if (child == mPeopleHubView) {
+ mLogger.logPosition(i, "CONVERSATIONS HEADER");
currentPeopleHeaderIdx = i;
continue;
}
if (child == mAlertingHeader) {
+ mLogger.logPosition(i, "ALERTING HEADER");
currentAlertingHeaderIdx = i;
continue;
}
if (child == mGentleHeader) {
+ mLogger.logPosition(i, "SILENT HEADER");
currentGentleHeaderIdx = i;
continue;
}
if (!(child instanceof ExpandableNotificationRow)) {
+ mLogger.logPosition(i, "other");
continue;
}
lastNotifIndex = i;
@@ -321,6 +383,7 @@
// Once we enter a new section, calculate the target position for the header.
switch (row.getEntry().getBucket()) {
case BUCKET_HEADS_UP:
+ mLogger.logPosition(i, "Heads Up");
if (showHeaders && incomingHeaderTarget == -1) {
incomingHeaderTarget = i;
// Offset the target if there are other headers before this that will be
@@ -346,6 +409,7 @@
}
break;
case BUCKET_PEOPLE:
+ mLogger.logPosition(i, "Conversation");
peopleNotifsPresent = true;
if (showHeaders && peopleHeaderTarget == -1) {
peopleHeaderTarget = i;
@@ -363,6 +427,7 @@
}
break;
case BUCKET_ALERTING:
+ mLogger.logPosition(i, "Alerting");
if (showHeaders && usingPeopleFiltering && alertingHeaderTarget == -1) {
alertingHeaderTarget = i;
// Offset the target if there are other headers before this that will be
@@ -376,6 +441,7 @@
}
break;
case BUCKET_SILENT:
+ mLogger.logPosition(i, "Silent");
if (showHeaders && gentleHeaderTarget == -1) {
gentleHeaderTarget = i;
// Offset the target if there are other headers before this that will be
@@ -406,6 +472,14 @@
}
}
+ mLogger.logStr("New header target positions:");
+
+ mLogger.logPosition(incomingHeaderTarget, "INCOMING HEADER");
+ mLogger.logPosition(mediaControlsTarget, "MEDIA CONTROLS");
+ mLogger.logPosition(peopleHeaderTarget, "CONVERSATIONS HEADER");
+ mLogger.logPosition(alertingHeaderTarget, "ALERTING HEADER");
+ mLogger.logPosition(gentleHeaderTarget, "SILENT HEADER");
+
// Add headers in reverse order to preserve indices
adjustHeaderVisibilityAndPosition(
gentleHeaderTarget, mGentleHeader, currentGentleHeaderIdx);
@@ -416,6 +490,13 @@
adjustViewPosition(mediaControlsTarget, mMediaControlsView, currentMediaControlsIdx);
adjustViewPosition(incomingHeaderTarget, mIncomingHeader, currentIncomingHeaderIdx);
+
+ mLogger.logStr("Final order:");
+
+ logShadeContents();
+
+ mLogger.logStr("Section boundary update complete");
+
// Update headers to reflect state of section contents
mGentleHeader.setAreThereDismissableGentleNotifs(
mParent.hasActiveClearableNotifications(ROWS_GENTLE));
@@ -588,7 +669,7 @@
void hidePeopleRow() {
mPeopleHubVisible = false;
- updateSectionBoundaries();
+ updateSectionBoundaries("PeopleHub dismissed");
}
void setHeaderForegroundColor(@ColorInt int color) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 7f32c00..1ccc2bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -29,7 +29,6 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
-import android.app.TaskStackBuilder;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeAnimator;
@@ -51,7 +50,6 @@
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -3074,6 +3072,9 @@
*/
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
private boolean generateRemoveAnimation(ExpandableView child) {
+ if (!child.wantsAddAndRemoveAnimations()) {
+ return false;
+ }
if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
mAddedHeadsUpChildren.remove(child);
return false;
@@ -3428,7 +3429,8 @@
@Override
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
- if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden()) {
+ if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden()
+ && child.wantsAddAndRemoveAnimations()) {
// Generate Animations
mChildrenToAddAnimated.add(child);
if (fromMoreCard) {
@@ -5841,7 +5843,7 @@
// Let's update the footer once the notifications have been updated (in the next frame)
post(() -> {
updateFooter();
- updateSectionBoundaries();
+ updateSectionBoundaries("dynamic privacy changed");
});
}
@@ -5922,8 +5924,8 @@
/** Updates the indices of the boundaries between sections. */
@ShadeViewRefactor(RefactorComponent.INPUT)
- public void updateSectionBoundaries() {
- mSectionsManager.updateSectionBoundaries();
+ public void updateSectionBoundaries(String reason) {
+ mSectionsManager.updateSectionBoundaries(reason);
}
private void updateContinuousBackgroundDrawing() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index 84dd48b..80785db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone;
import android.annotation.Nullable;
-import android.app.NotificationChannel;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
@@ -28,6 +27,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -42,6 +42,8 @@
import javax.inject.Inject;
import javax.inject.Singleton;
+import dagger.Lazy;
+
/**
* A class to handle notifications and their corresponding groups.
*/
@@ -51,6 +53,7 @@
private static final String TAG = "NotificationGroupManager";
private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
private final ArraySet<OnGroupChangeListener> mListeners = new ArraySet<>();
+ private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
private int mBarState = -1;
private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
private HeadsUpManager mHeadsUpManager;
@@ -58,8 +61,11 @@
@Nullable private BubbleController mBubbleController = null;
@Inject
- public NotificationGroupManager(StatusBarStateController statusBarStateController) {
+ public NotificationGroupManager(
+ StatusBarStateController statusBarStateController,
+ Lazy<PeopleNotificationIdentifier> peopleNotificationIdentifier) {
statusBarStateController.addCallback(this);
+ mPeopleNotificationIdentifier = peopleNotificationIdentifier;
}
private BubbleController getBubbleController() {
@@ -536,8 +542,9 @@
if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) {
return false;
}
- NotificationChannel channel = entry.getChannel();
- if (channel != null && channel.isImportantConversation()) {
+ int peopleNotificationType = mPeopleNotificationIdentifier.get().getPeopleNotificationType(
+ entry.getSbn(), entry.getRanking());
+ if (peopleNotificationType == PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON) {
return true;
}
if (mHeadsUpManager != null && !mHeadsUpManager.isAlerting(entry.getKey())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index c9716d3..35c33ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -69,6 +69,7 @@
import com.android.systemui.doze.DozeLog;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -242,6 +243,7 @@
private final KeyguardBypassController mKeyguardBypassController;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final ConversationNotificationManager mConversationNotificationManager;
+ private final MediaHierarchyManager mMediaHierarchyManager;
private KeyguardAffordanceHelper mAffordanceHelper;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
@@ -456,7 +458,8 @@
ConfigurationController configurationController,
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
- ConversationNotificationManager conversationNotificationManager) {
+ ConversationNotificationManager conversationNotificationManager,
+ MediaHierarchyManager mediaHierarchyManager) {
super(view, falsingManager, dozeLog, keyguardStateController,
(SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
@@ -466,6 +469,7 @@
mZenModeController = zenModeController;
mConfigurationController = configurationController;
mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
+ mMediaHierarchyManager = mediaHierarchyManager;
mView.setWillNotDraw(!DEBUG);
mInjectionInflationController = injectionInflationController;
mFalsingManager = falsingManager;
@@ -1609,7 +1613,7 @@
if (mQs == null) return;
float qsExpansionFraction = getQsExpansionFraction();
mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
- int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight();
+ mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
}
@@ -2880,8 +2884,8 @@
return mNotificationStackScroller.createDelegate();
}
- public void updateNotificationViews() {
- mNotificationStackScroller.updateSectionBoundaries();
+ void updateNotificationViews(String reason) {
+ mNotificationStackScroller.updateSectionBoundaries(reason);
mNotificationStackScroller.updateSpeedBumpIndex();
mNotificationStackScroller.updateFooter();
updateShowEmptyShadeView();
@@ -3514,7 +3518,11 @@
// Calculate quick setting heights.
int oldMaxHeight = mQsMaxExpansionHeight;
if (mQs != null) {
+ float previousMin = mQsMinExpansionHeight;
mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
+ if (mQsExpansionHeight == previousMin) {
+ mQsExpansionHeight = mQsMinExpansionHeight;
+ }
mQsMaxExpansionHeight = mQs.getDesiredHeight();
mNotificationStackScroller.setMaxTopPadding(
mQsMaxExpansionHeight + mQsNotificationTopPadding);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
index 567ddb6..926c1bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
@@ -279,7 +279,7 @@
} else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
// Make sure to remove FLAG_ALT_FOCUSABLE_IM when keyguard needs input.
- if (state.mKeyguardNeedsInput) {
+ if (state.mKeyguardNeedsInput && state.isKeyguardShowingAndNotOccluded()) {
mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else {
mLpChanged.flags |= LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -321,8 +321,7 @@
|| state.mPanelVisible || state.mKeyguardFadingAway || state.mBouncerShowing
|| state.mHeadsUpShowing
|| state.mScrimsVisibility != ScrimController.TRANSPARENT)
- || state.mBackgroundBlurRadius > 0
- || state.mLaunchingActivity;
+ || state.mBackgroundBlurRadius > 0;
}
private void applyFitsSystemWindows(State state) {
@@ -486,11 +485,6 @@
apply(mCurrentState);
}
- void setLaunchingActivity(boolean launching) {
- mCurrentState.mLaunchingActivity = launching;
- apply(mCurrentState);
- }
-
public void setScrimsVisibility(int scrimsVisibility) {
mCurrentState.mScrimsVisibility = scrimsVisibility;
apply(mCurrentState);
@@ -651,7 +645,6 @@
boolean mForceCollapsed;
boolean mForceDozeBrightness;
boolean mForceUserActivity;
- boolean mLaunchingActivity;
boolean mBackdropShowing;
boolean mWallpaperSupportsAmbientMode;
boolean mNotTouchable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 0d25898..596a607 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -93,7 +93,6 @@
private PhoneStatusBarView mStatusBarView;
private PhoneStatusBarTransitions mBarTransitions;
private StatusBar mService;
- private NotificationShadeWindowController mNotificationShadeWindowController;
private DragDownHelper mDragDownHelper;
private boolean mDoubleTapEnabled;
private boolean mSingleTapEnabled;
@@ -431,14 +430,10 @@
public void setExpandAnimationPending(boolean pending) {
mExpandAnimationPending = pending;
- mNotificationShadeWindowController
- .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
}
public void setExpandAnimationRunning(boolean running) {
mExpandAnimationRunning = running;
- mNotificationShadeWindowController
- .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
}
public void cancelExpandHelper() {
@@ -461,9 +456,8 @@
}
}
- public void setService(StatusBar statusBar, NotificationShadeWindowController controller) {
+ public void setService(StatusBar statusBar) {
mService = statusBar;
- mNotificationShadeWindowController = controller;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 56510dd..d88cbab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1001,7 +1001,7 @@
updateTheme();
inflateStatusBarWindow();
- mNotificationShadeWindowViewController.setService(this, mNotificationShadeWindowController);
+ mNotificationShadeWindowViewController.setService(this);
mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
// TODO: Deal with the ugliness that comes from having some of the statusbar broken out
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index aecbb90..84da35b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -296,20 +296,20 @@
}
@Override
- public void updateNotificationViews() {
+ public void updateNotificationViews(final String reason) {
// The function updateRowStates depends on both of these being non-null, so check them here.
// We may be called before they are set from DeviceProvisionedController's callback.
if (mScrimController == null) return;
// Do not modify the notifications during collapse.
if (isCollapsing()) {
- mShadeController.addPostCollapseAction(this::updateNotificationViews);
+ mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason));
return;
}
mViewHierarchyManager.updateNotificationViews();
- mNotificationPanel.updateNotificationViews();
+ mNotificationPanel.updateNotificationViews(reason);
}
public void onNotificationRemoved(String key, StatusBarNotification old) {
@@ -347,7 +347,7 @@
updateNotificationOnUiModeChanged();
mDispatchUiModeChangeOnUserSwitched = false;
}
- updateNotificationViews();
+ updateNotificationViews("user switched");
mMediaManager.clearCurrentMediaNotification();
mStatusBar.setLockscreenUser(newUserId);
updateMediaMetaData(true, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index f8da03a..df3748a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -298,6 +298,7 @@
convertView.setAlpha(
item.isCurrent || item.isSwitchToEnabled ? USER_SWITCH_ENABLED_ALPHA
: USER_SWITCH_DISABLED_ALPHA);
+ convertView.setEnabled(item.isSwitchToEnabled);
return convertView;
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 6a9d9b6..b1792d0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -130,7 +130,7 @@
*/
public static boolean useQsMediaPlayer(Context context) {
int flag = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 0);
+ Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1);
return flag > 0;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/MeasurementCache.kt b/packages/SystemUI/src/com/android/systemui/util/animation/MeasurementCache.kt
new file mode 100644
index 0000000..2be698b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/MeasurementCache.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.util.animation
+
+/**
+ * A class responsible for caching view Measurements which guarantees that we always obtain a value
+ */
+class GuaranteedMeasurementCache constructor(
+ private val baseCache : MeasurementCache,
+ private val inputMapper: (MeasurementInput) -> MeasurementInput,
+ private val measurementProvider: (MeasurementInput) -> MeasurementOutput?
+) : MeasurementCache {
+
+ override fun obtainMeasurement(input: MeasurementInput) : MeasurementOutput {
+ val mappedInput = inputMapper.invoke(input)
+ if (!baseCache.contains(mappedInput)) {
+ var measurement = measurementProvider.invoke(mappedInput)
+ if (measurement != null) {
+ // Only cache measurings that actually have a size
+ baseCache.putMeasurement(mappedInput, measurement)
+ } else {
+ measurement = MeasurementOutput(0, 0)
+ }
+ return measurement
+ } else {
+ return baseCache.obtainMeasurement(mappedInput)
+ }
+ }
+
+ override fun contains(input: MeasurementInput): Boolean {
+ return baseCache.contains(inputMapper.invoke(input))
+ }
+
+ override fun putMeasurement(input: MeasurementInput, output: MeasurementOutput) {
+ if (output.measuredWidth == 0 || output.measuredHeight == 0) {
+ // Only cache measurings that actually have a size
+ return;
+ }
+ val remappedInput = inputMapper.invoke(input)
+ baseCache.putMeasurement(remappedInput, output)
+ }
+}
+
+/**
+ * A base implementation class responsible for caching view Measurements
+ */
+class BaseMeasurementCache : MeasurementCache {
+ private val dataCache: MutableMap<MeasurementInput, MeasurementOutput> = mutableMapOf()
+
+ override fun obtainMeasurement(input: MeasurementInput) : MeasurementOutput {
+ val measurementOutput = dataCache[input]
+ if (measurementOutput == null) {
+ return MeasurementOutput(0, 0)
+ } else {
+ return measurementOutput
+ }
+ }
+
+ override fun contains(input: MeasurementInput) : Boolean {
+ return dataCache[input] != null
+ }
+
+ override fun putMeasurement(input: MeasurementInput, output: MeasurementOutput) {
+ dataCache[input] = output
+ }
+}
+
+interface MeasurementCache {
+ fun obtainMeasurement(input: MeasurementInput) : MeasurementOutput
+ fun contains(input: MeasurementInput) : Boolean
+ fun putMeasurement(input: MeasurementInput, output: MeasurementOutput)
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt b/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt
new file mode 100644
index 0000000..bf94c5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.util.animation
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.View
+import android.widget.FrameLayout
+
+/**
+ * A special view that is designed to host a single "unique object". The unique object is
+ * dynamically added and removed from this view and may transition to other UniqueObjectHostViews
+ * available in the system.
+ * This is useful to share a singular instance of a view that can transition between completely
+ * independent parts of the view hierarchy.
+ * If the view currently hosts the unique object, it's measuring it normally,
+ * but if it's not attached, it will obtain the size by requesting a measure, as if it were
+ * always attached.
+ */
+class UniqueObjectHostView(
+ context: Context
+) : FrameLayout(context) {
+ lateinit var measurementCache : GuaranteedMeasurementCache
+ var onMeasureListener: ((MeasurementInput) -> Unit)? = null
+
+ @SuppressLint("DrawAllocation")
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val paddingHorizontal = paddingStart + paddingEnd
+ val paddingVertical = paddingTop + paddingBottom
+ val width = MeasureSpec.getSize(widthMeasureSpec) - paddingHorizontal
+ val widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.getMode(widthMeasureSpec))
+ val height = MeasureSpec.getSize(heightMeasureSpec) - paddingVertical
+ val heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.getMode(heightMeasureSpec))
+ val measurementInput = MeasurementInputData(widthSpec, heightSpec)
+ onMeasureListener?.apply {
+ invoke(measurementInput)
+ }
+ if (!isCurrentHost()) {
+ // We're not currently the host, let's get the dimension from our cache (this might
+ // perform a measuring if the cache doesn't have it yet)
+ // The goal here is that the view will always have a consistent measuring, regardless
+ // if it's attached or not.
+ // The behavior is therefore very similar to the view being persistently attached to
+ // this host, which can prevent flickers. It also makes sure that we always know
+ // the size of the view during transitions even if it has never been attached here
+ // before.
+ val (cachedWidth, cachedHeight) = measurementCache.obtainMeasurement(measurementInput)
+ setMeasuredDimension(cachedWidth + paddingHorizontal, cachedHeight + paddingVertical)
+ } else {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ // Let's update our cache
+ val child = getChildAt(0)!!
+ val output = MeasurementOutput(child.measuredWidth, child.measuredHeight)
+ measurementCache.putMeasurement(measurementInput, output)
+ }
+ }
+
+ private fun isCurrentHost() = childCount != 0
+}
+
+/**
+ * A basic view measurement input
+ */
+interface MeasurementInput {
+ fun sameAs(input: MeasurementInput?): Boolean {
+ return equals(input)
+ }
+ val width : Int
+ get() {
+ return View.MeasureSpec.getSize(widthMeasureSpec)
+ }
+ val height : Int
+ get() {
+ return View.MeasureSpec.getSize(heightMeasureSpec)
+ }
+ var widthMeasureSpec: Int
+ var heightMeasureSpec: Int
+}
+
+/**
+ * The output of a view measurement
+ */
+data class MeasurementOutput(
+ val measuredWidth: Int,
+ val measuredHeight: Int
+)
+
+/**
+ * The data object holding a basic view measurement input
+ */
+data class MeasurementInputData(
+ override var widthMeasureSpec: Int,
+ override var heightMeasureSpec: Int
+) : MeasurementInput
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
deleted file mode 100644
index 4bcf917..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard
-
-import android.app.Notification
-import android.graphics.drawable.Icon
-import android.media.MediaMetadata
-import android.media.session.MediaController
-import android.media.session.MediaSession
-import android.media.session.PlaybackState
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.widget.TextView
-import androidx.arch.core.executor.ArchTaskExecutor
-import androidx.arch.core.executor.TaskExecutor
-import androidx.test.filters.SmallTest
-
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.media.MediaControllerFactory
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-public class KeyguardMediaPlayerTest : SysuiTestCase() {
-
- private lateinit var keyguardMediaPlayer: KeyguardMediaPlayer
- @Mock private lateinit var mockMediaFactory: MediaControllerFactory
- @Mock private lateinit var mockMediaController: MediaController
- private lateinit var playbackState: PlaybackState
- private lateinit var fakeExecutor: FakeExecutor
- private lateinit var mediaMetadata: MediaMetadata.Builder
- private lateinit var entry: NotificationEntry
- @Mock private lateinit var mockView: View
- private lateinit var songView: TextView
- private lateinit var artistView: TextView
- @Mock private lateinit var mockIcon: Icon
-
- private val taskExecutor: TaskExecutor = object : TaskExecutor() {
- public override fun executeOnDiskIO(runnable: Runnable) {
- runnable.run()
- }
- public override fun postToMainThread(runnable: Runnable) {
- runnable.run()
- }
- public override fun isMainThread(): Boolean {
- return true
- }
- }
-
- @Before
- public fun setup() {
- playbackState = PlaybackState.Builder().run {
- build()
- }
- mockMediaController = mock(MediaController::class.java)
- whenever(mockMediaController.getPlaybackState()).thenReturn(playbackState)
- mockMediaFactory = mock(MediaControllerFactory::class.java)
- whenever(mockMediaFactory.create(any())).thenReturn(mockMediaController)
-
- fakeExecutor = FakeExecutor(FakeSystemClock())
- keyguardMediaPlayer = KeyguardMediaPlayer(context, mockMediaFactory, fakeExecutor)
- mockIcon = mock(Icon::class.java)
-
- mockView = mock(View::class.java)
- songView = TextView(context)
- artistView = TextView(context)
- whenever<TextView>(mockView.findViewById(R.id.header_title)).thenReturn(songView)
- whenever<TextView>(mockView.findViewById(R.id.header_artist)).thenReturn(artistView)
-
- mediaMetadata = MediaMetadata.Builder()
- entry = NotificationEntryBuilder().build()
- entry.getSbn().getNotification().extras.putParcelable(Notification.EXTRA_MEDIA_SESSION,
- MediaSession.Token(1, null))
-
- ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
-
- keyguardMediaPlayer.bindView(mockView)
- }
-
- @After
- public fun tearDown() {
- keyguardMediaPlayer.unbindView()
- ArchTaskExecutor.getInstance().setDelegate(null)
- }
-
- @Test
- public fun testBind() {
- keyguardMediaPlayer.unbindView()
- keyguardMediaPlayer.bindView(mockView)
- }
-
- @Test
- public fun testUnboundClearControls() {
- keyguardMediaPlayer.unbindView()
- keyguardMediaPlayer.clearControls()
- keyguardMediaPlayer.bindView(mockView)
- }
-
- @Test
- public fun testUpdateControls() {
- keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
- FakeExecutor.exhaustExecutors(fakeExecutor)
- verify(mockView).setVisibility(View.VISIBLE)
- }
-
- @Test
- public fun testClearControls() {
- keyguardMediaPlayer.clearControls()
- FakeExecutor.exhaustExecutors(fakeExecutor)
- verify(mockView).setVisibility(View.GONE)
- }
-
- @Test
- public fun testUpdateControlsNullPlaybackState() {
- // GIVEN that the playback state is null (ie. the media session was destroyed)
- whenever(mockMediaController.getPlaybackState()).thenReturn(null)
- // WHEN updated
- keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
- FakeExecutor.exhaustExecutors(fakeExecutor)
- // THEN the controls are cleared (ie. visibility is set to GONE)
- verify(mockView).setVisibility(View.GONE)
- }
-
- @Test
- public fun testSongName() {
- val song: String = "Song"
- mediaMetadata.putText(MediaMetadata.METADATA_KEY_TITLE, song)
-
- keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
-
- assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
- assertThat(songView.getText()).isEqualTo(song)
- }
-
- @Test
- public fun testArtistName() {
- val artist: String = "Artist"
- mediaMetadata.putText(MediaMetadata.METADATA_KEY_ARTIST, artist)
-
- keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
-
- assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
- assertThat(artistView.getText()).isEqualTo(artist)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index b5609dd..7bc453a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -38,6 +38,7 @@
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
+import android.app.trust.IStrongAuthTracker;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -72,6 +73,8 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -120,6 +123,10 @@
@Mock
private TrustManager mTrustManager;
@Mock
+ private LockPatternUtils mLockPatternUtils;
+ @Mock
+ private ILockSettings mLockSettings;
+ @Mock
private FingerprintManager mFingerprintManager;
@Mock
private FaceManager mFaceManager;
@@ -169,12 +176,13 @@
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
when(mUserManager.isPrimaryUser()).thenReturn(true);
+ when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class));
when(mStrongAuthTracker
.isUnlockingWithBiometricAllowed(anyBoolean() /* isStrongBiometric */))
.thenReturn(true);
-
when(mTelephonyManager.getServiceStateForSubscriber(anyInt()))
.thenReturn(new ServiceState());
+ when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings);
mSpiedContext.addMockSystemService(TrustManager.class, mTrustManager);
mSpiedContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
mSpiedContext.addMockSystemService(BiometricManager.class, mBiometricManager);
@@ -729,8 +737,9 @@
super(context,
TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
mBroadcastDispatcher, mDumpManager,
- mRingerModeTracker, mBackgroundExecutor, mStatusBarStateController);
- mStrongAuthTracker = KeyguardUpdateMonitorTest.this.mStrongAuthTracker;
+ mRingerModeTracker, mBackgroundExecutor,
+ mStatusBarStateController, mLockPatternUtils);
+ setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
public boolean hasSimStateJustChanged() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index e2f303e..96e868d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -279,7 +279,8 @@
mFloatingContentCoordinator,
mDataRepository,
mSysUiState,
- mock(INotificationManager.class));
+ mock(INotificationManager.class),
+ mWindowManager);
mBubbleController.setExpandListener(mBubbleExpandListener);
// Get a reference to the BubbleController's entry listener
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 8a83b84..73b8760 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -254,7 +254,8 @@
mFloatingContentCoordinator,
mDataRepository,
mSysUiState,
- mock(INotificationManager.class));
+ mock(INotificationManager.class),
+ mWindowManager);
mBubbleController.addNotifCallback(mNotifCallback);
mBubbleController.setExpandListener(mBubbleExpandListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
index 1542b86..bdb7944 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
@@ -18,6 +18,7 @@
import android.app.INotificationManager;
import android.content.Context;
+import android.view.WindowManager;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
@@ -57,13 +58,15 @@
FloatingContentCoordinator floatingContentCoordinator,
BubbleDataRepository dataRepository,
SysUiState sysUiState,
- INotificationManager notificationManager) {
+ INotificationManager notificationManager,
+ WindowManager windowManager) {
super(context,
notificationShadeWindowController, statusBarStateController, shadeController,
data, Runnable::run, configurationController, interruptionStateProvider,
zenModeController, lockscreenUserManager, groupManager, entryManager,
notifPipeline, featureFlags, dumpManager, floatingContentCoordinator,
- dataRepository, sysUiState, notificationManager);
+ dataRepository, sysUiState, notificationManager,
+ windowManager);
setInflateSynchronously(true);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
index 9629079..eb43b81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
@@ -34,6 +34,8 @@
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
+import com.android.systemui.SysuiTestCase;
+
import org.junit.Rule;
import org.junit.Test;
@@ -41,7 +43,7 @@
import java.util.function.BooleanSupplier;
@LargeTest
-public class GlobalActionsImeTest {
+public class GlobalActionsImeTest extends SysuiTestCase {
@Rule
public ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 92c1d76..f70fb4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -131,7 +131,7 @@
MediaMetadata metadata = mock(MediaMetadata.class);
when(metadata.getText(any())).thenReturn("metadata");
mProvider.onDozingChanged(true);
- mProvider.onMetadataOrStateChanged(metadata, PlaybackState.STATE_PLAYING);
+ mProvider.onPrimaryMetadataOrStateChanged(metadata, PlaybackState.STATE_PLAYING);
mProvider.onBindSlice(mProvider.getUri());
verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_TITLE));
verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_ARTIST));
@@ -144,7 +144,7 @@
when(metadata.getText(any())).thenReturn("metadata");
when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
- mProvider.onMetadataOrStateChanged(metadata, PlaybackState.STATE_PLAYING);
+ mProvider.onPrimaryMetadataOrStateChanged(metadata, PlaybackState.STATE_PLAYING);
mProvider.onBindSlice(mProvider.getUri());
verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_TITLE));
verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_ARTIST));
@@ -210,7 +210,8 @@
mProvider.onStateChanged(StatusBarState.KEYGUARD);
mProvider.onDozingChanged(true);
reset(mContentResolver);
- mProvider.onMetadataOrStateChanged(mock(MediaMetadata.class), PlaybackState.STATE_PLAYING);
+ mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
+ PlaybackState.STATE_PLAYING);
verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
// Hides after waking up
@@ -222,7 +223,8 @@
@Test
public void onDozingChanged_updatesSliceIfMedia() {
mProvider.onStateChanged(StatusBarState.KEYGUARD);
- mProvider.onMetadataOrStateChanged(mock(MediaMetadata.class), PlaybackState.STATE_PLAYING);
+ mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
+ PlaybackState.STATE_PLAYING);
reset(mContentResolver);
// Show media when dozing
mProvider.onDozingChanged(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index 128d6e5..6c543c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.customize.QSCustomizer;
@@ -50,7 +51,6 @@
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
import org.junit.Before;
import org.junit.Test;
@@ -62,7 +62,6 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
-import java.util.concurrent.Executor;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -90,9 +89,7 @@
@Mock
private QSTileView mQSTileView;
@Mock
- private Executor mForegroundExecutor;
- @Mock
- private DelayableExecutor mBackgroundExecutor;
+ private MediaHost mMediaHost;
@Mock
private LocalBluetoothManager mLocalBluetoothManager;
@Mock
@@ -116,8 +113,7 @@
mTestableLooper.runWithLooper(() -> {
mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher,
- mQSLogger, mForegroundExecutor, mBackgroundExecutor,
- mLocalBluetoothManager, mActivityStarter, mEntryManager, mUiEventLogger);
+ mQSLogger, mMediaHost, mUiEventLogger);
// Provides a parent with non-zero size for QSPanel
mParentView = new FrameLayout(mContext);
mParentView.addView(mQsPanel);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index bb7f73a..d583048 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -243,7 +243,7 @@
// Ensure that update callbacks happen in correct order
InOrder order = inOrder(mEntryListener, mPresenter, mEntryListener);
order.verify(mEntryListener).onPreEntryUpdated(mEntry);
- order.verify(mPresenter).updateNotificationViews();
+ order.verify(mPresenter).updateNotificationViews(any());
order.verify(mEntryListener).onPostEntryUpdated(mEntry);
}
@@ -254,7 +254,7 @@
mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
- verify(mPresenter).updateNotificationViews();
+ verify(mPresenter).updateNotificationViews(any());
verify(mEntryListener).onEntryRemoved(
eq(mEntry), any(), eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
verify(mRow).setRemoved();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index 277ac24..595ba89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -45,6 +45,7 @@
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -93,7 +94,9 @@
.thenReturn(PackageManager.PERMISSION_GRANTED);
mDependency.injectTestDependency(ForegroundServiceController.class, mFsc);
mDependency.injectTestDependency(NotificationGroupManager.class,
- new NotificationGroupManager(mock(StatusBarStateController.class)));
+ new NotificationGroupManager(
+ mock(StatusBarStateController.class),
+ () -> mock(PeopleNotificationIdentifier.class)));
mDependency.injectMockDependency(ShadeController.class);
mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index 2894abb8..7dfead7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -335,7 +335,7 @@
assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
// THEN we update the presenter
- verify(mPresenter).updateNotificationViews();
+ verify(mPresenter).updateNotificationViews(any());
}
@Test
@@ -364,7 +364,7 @@
verify(mEntryListener).onEntryReinflated(entry);
// THEN we update the presenter
- verify(mPresenter).updateNotificationViews();
+ verify(mPresenter).updateNotificationViews(any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 07f2085..b9eb4d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -115,7 +115,9 @@
dependency.injectMockDependency(BubbleController.class);
dependency.injectMockDependency(NotificationShadeWindowController.class);
mStatusBarStateController = mock(StatusBarStateController.class);
- mGroupManager = new NotificationGroupManager(mStatusBarStateController);
+ mGroupManager = new NotificationGroupManager(
+ mStatusBarStateController,
+ () -> mock(PeopleNotificationIdentifier.class));
mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController,
mock(KeyguardBypassController.class), mock(NotificationGroupManager.class),
mock(ConfigurationControllerImpl.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 646bc96..0b86a78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -42,9 +42,9 @@
import androidx.test.filters.SmallTest;
-import com.android.keyguard.KeyguardMediaPlayer;
import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
@@ -74,10 +74,11 @@
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private ConfigurationController mConfigurationController;
@Mock private PeopleHubViewAdapter mPeopleHubAdapter;
- @Mock private KeyguardMediaPlayer mKeyguardMediaPlayer;
+ @Mock private KeyguardMediaController mKeyguardMediaController;
@Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
@Mock private NotificationRowComponent mNotificationRowComponent;
@Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
+ @Mock private NotificationSectionsLogger mLogger;
private NotificationSectionsManager mSectionsManager;
@@ -93,8 +94,9 @@
mStatusBarStateController,
mConfigurationController,
mPeopleHubAdapter,
- mKeyguardMediaPlayer,
- mSectionsFeatureManager
+ mKeyguardMediaController,
+ mSectionsFeatureManager,
+ mLogger
);
// Required in order for the header inflation to work properly
when(mNssl.generateLayoutParams(any(AttributeSet.class)))
@@ -367,38 +369,6 @@
verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 1);
}
- @Test
- public void testMediaControls_RemoveWhenExitKeyguard() {
- enableMediaControls();
-
- // GIVEN a stack with media controls
- setStackState(ChildType.MEDIA_CONTROLS, ChildType.ALERTING, ChildType.GENTLE_HEADER,
- ChildType.GENTLE);
-
- // WHEN we leave the keyguard
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
- mSectionsManager.updateSectionBoundaries();
-
- // Then the media controls is removed
- verify(mNssl).removeView(mSectionsManager.getMediaControlsView());
- }
-
- @Test
- public void testMediaControls_RemoveWhenPullDownShade() {
- enableMediaControls();
-
- // GIVEN a stack with media controls
- setStackState(ChildType.MEDIA_CONTROLS, ChildType.ALERTING, ChildType.GENTLE_HEADER,
- ChildType.GENTLE);
-
- // WHEN we pull down the shade on the keyguard
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
- mSectionsManager.updateSectionBoundaries();
-
- // Then the media controls is removed
- verify(mNssl).removeView(mSectionsManager.getMediaControlsView());
- }
-
private void enablePeopleFiltering() {
when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true);
when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index 67f94130..885dff3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
import com.android.systemui.statusbar.notification.row.RowContentBindParams;
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
@@ -87,7 +88,9 @@
when(mNotificationEntryManager.getPendingNotificationsIterator())
.thenReturn(mPendingEntries.values());
- mGroupManager = new NotificationGroupManager(mock(StatusBarStateController.class));
+ mGroupManager = new NotificationGroupManager(
+ mock(StatusBarStateController.class),
+ () -> mock(PeopleNotificationIdentifier.class));
mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
index 19ce1ea..5a6f74a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
@@ -33,6 +33,7 @@
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
@@ -63,7 +64,9 @@
}
private void initializeGroupManager() {
- mGroupManager = new NotificationGroupManager(mock(StatusBarStateController.class));
+ mGroupManager = new NotificationGroupManager(
+ mock(StatusBarStateController.class),
+ () -> mock(PeopleNotificationIdentifier.class));
mGroupManager.setHeadsUpManager(mHeadsUpManager);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 57ef055..b5663d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -55,6 +55,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.doze.DozeLog;
+import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -172,6 +173,8 @@
@Mock
private ConfigurationController mConfigurationController;
@Mock
+ private MediaHierarchyManager mMediaHiearchyManager;
+ @Mock
private ConversationNotificationManager mConversationNotificationManager;
private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
@@ -228,7 +231,7 @@
mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
mFlingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
- mConversationNotificationManager);
+ mConversationNotificationManager, mMediaHiearchyManager);
mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
mNotificationShelf, mNotificationAreaController, mScrimController);
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
index 5320ecd..ca6c16f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
@@ -16,11 +16,15 @@
package com.android.systemui.statusbar.phone;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -44,6 +48,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -62,6 +67,7 @@
@Mock private SysuiColorExtractor mColorExtractor;
@Mock ColorExtractor.GradientColors mGradientColors;
@Mock private DumpManager mDumpManager;
+ @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
private NotificationShadeWindowController mNotificationShadeWindowController;
@@ -121,4 +127,46 @@
mNotificationShadeWindowController.setBackgroundBlurRadius(0);
verify(mNotificationShadeWindowView).setVisibility(eq(View.INVISIBLE));
}
+
+ @Test
+ public void setBouncerShowing_isFocusable_whenNeedsInput() {
+ mNotificationShadeWindowController.setKeyguardNeedsInput(true);
+ clearInvocations(mWindowManager);
+ mNotificationShadeWindowController.setBouncerShowing(true);
+
+ verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+ assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) == 0).isTrue();
+ assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) == 0).isTrue();
+ }
+
+ @Test
+ public void setKeyguardShowing_focusable_notAltFocusable_whenNeedsInput() {
+ mNotificationShadeWindowController.setKeyguardShowing(true);
+ clearInvocations(mWindowManager);
+ mNotificationShadeWindowController.setKeyguardNeedsInput(true);
+
+ verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+ assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) == 0).isTrue();
+ assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) == 0).isTrue();
+ }
+
+ @Test
+ public void setPanelExpanded_notFocusable_altFocusable_whenPanelIsOpen() {
+ mNotificationShadeWindowController.setPanelExpanded(true);
+ clearInvocations(mWindowManager);
+ mNotificationShadeWindowController.setNotificationShadeFocusable(true);
+
+ verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+ assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) == 0).isTrue();
+ assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) != 0).isTrue();
+ }
+
+ @Test
+ public void setKeyguardShowing_notFocusable_byDefault() {
+ mNotificationShadeWindowController.setKeyguardShowing(false);
+
+ verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+ assertThat((mLayoutParameters.getValue().flags & FLAG_NOT_FOCUSABLE) != 0).isTrue();
+ assertThat((mLayoutParameters.getValue().flags & FLAG_ALT_FOCUSABLE_IM) == 0).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index e04d25b..cc2d1c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -83,7 +83,6 @@
@Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
@Mock private NotificationShadeDepthController mNotificationShadeDepthController;
@Mock private SuperStatusBarViewFactory mStatusBarViewFactory;
- @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@Before
public void setUp() {
@@ -122,7 +121,7 @@
mNotificationPanelViewController,
mStatusBarViewFactory);
mController.setupExpandedStatusBar();
- mController.setService(mStatusBar, mNotificationShadeWindowController);
+ mController.setService(mStatusBar);
mController.setDragDownHelper(mDragDownHelper);
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 086a8be..ef17d13 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -19,6 +19,7 @@
import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
+import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
import static android.view.autofill.AutofillManager.ACTION_RESPONSE_EXPIRED;
import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
@@ -653,6 +654,10 @@
return mService.isInlineSuggestionsEnabled();
}
+ private boolean isViewFocusedLocked(int flags) {
+ return (flags & FLAG_VIEW_NOT_FOCUSED) == 0;
+ }
+
/**
* Clears the existing response for the partition, reads a new structure, and then requests a
* new fill response from the fill service.
@@ -711,10 +716,13 @@
cancelCurrentRequestLocked();
// Only ask IME to create inline suggestions request if Autofill provider supports it and
- // the render service is available.
+ // the render service is available except the autofill is triggered manually and the view
+ // is also not focused.
final RemoteInlineSuggestionRenderService remoteRenderService =
mService.getRemoteInlineSuggestionRenderServiceLocked();
- if (isInlineSuggestionsEnabledByAutofillProviderLocked() && remoteRenderService != null) {
+ if (isInlineSuggestionsEnabledByAutofillProviderLocked()
+ && remoteRenderService != null
+ && isViewFocusedLocked(flags)) {
Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
mAssistReceiver.newAutofillRequestLocked(viewState,
/*isInlineRequest=*/ true);
@@ -3139,9 +3147,9 @@
}
};
- // When the inline suggestion render service is available, there are 2 cases when
- // augmented autofill should ask IME for inline suggestion request, because standard
- // autofill flow didn't:
+ // When the inline suggestion render service is available and the view is focused, there
+ // are 2 cases when augmented autofill should ask IME for inline suggestion request,
+ // because standard autofill flow didn't:
// 1. the field is augmented autofill only (when standard autofill provider is None or
// when it returns null response)
// 2. standard autofill provider doesn't support inline suggestion
@@ -3149,7 +3157,8 @@
mService.getRemoteInlineSuggestionRenderServiceLocked();
if (remoteRenderService != null
&& (mForAugmentedAutofillOnly
- || !isInlineSuggestionsEnabledByAutofillProviderLocked())) {
+ || !isInlineSuggestionsEnabledByAutofillProviderLocked())
+ && isViewFocusedLocked(flags)) {
if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback(
(extras) -> {
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index f688759..fa84427 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -315,4 +315,15 @@
* @return {@code true} if the pruning was successful, {@code false} otherwise
*/
public abstract boolean pruneUninstalledPackagesData(@UserIdInt int userId);
+
+ /**
+ * Called by {@link com.android.server.usage.UsageStatsIdleService} between 24 to 48 hours of
+ * when the user is first unlocked to update the usage stats package mappings data that might
+ * be stale or have existed from a restore and belongs to packages that are not installed for
+ * this user anymore.
+ * Note: this is only executed for the system user.
+ *
+ * @return {@code true} if the updating was successful, {@code false} otherwise
+ */
+ public abstract boolean updatePackageMappingsData();
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 905c489..6402e07 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -1776,7 +1776,7 @@
socketRecord =
userRecord.mEncapSocketRecords.getResourceOrThrow(c.getEncapSocketResourceId());
}
- SpiRecord spiRecord = userRecord.mSpiRecords.getResourceOrThrow(c.getSpiResourceId());
+ SpiRecord spiRecord = transformInfo.getSpiRecord();
int mark =
(direction == IpSecManager.DIRECTION_OUT)
@@ -1809,7 +1809,7 @@
// Set outbound SPI only. We want inbound to use any valid SA (old, new) on rekeys,
// but want to guarantee outbound packets are sent over the new SA.
- spi = transformInfo.getSpiRecord().getSpi();
+ spi = spiRecord.getSpi();
}
// Always update the policy with the relevant XFRM_IF_ID
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 29eb64a..62ea63a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1942,10 +1942,13 @@
mDownloadsAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid);
}
- try {
- mIAppOpsService.startWatchingMode(OP_REQUEST_INSTALL_PACKAGES, null, mAppOpsCallback);
- mIAppOpsService.startWatchingMode(OP_LEGACY_STORAGE, null, mAppOpsCallback);
- } catch (RemoteException e) {
+ if (!mIsFuseEnabled) {
+ try {
+ mIAppOpsService.startWatchingMode(OP_REQUEST_INSTALL_PACKAGES, null,
+ mAppOpsCallback);
+ mIAppOpsService.startWatchingMode(OP_LEGACY_STORAGE, null, mAppOpsCallback);
+ } catch (RemoteException e) {
+ }
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 63e01e0..5ebfb00 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2582,6 +2582,28 @@
}
}
+ private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
+ int op, int uid, String packageName) {
+ boolean duplicate = false;
+ if (reports == null) {
+ reports = new ArrayList<>();
+ } else {
+ final int reportCount = reports.size();
+ for (int j = 0; j < reportCount; j++) {
+ ChangeRec report = reports.get(j);
+ if (report.op == op && report.pkg.equals(packageName)) {
+ duplicate = true;
+ break;
+ }
+ }
+ }
+ if (!duplicate) {
+ reports.add(new ChangeRec(op, uid, packageName));
+ }
+
+ return reports;
+ }
+
private static HashMap<ModeCallback, ArrayList<ChangeRec>> addCallbacks(
HashMap<ModeCallback, ArrayList<ChangeRec>> callbacks,
int op, int uid, String packageName, ArraySet<ModeCallback> cbs) {
@@ -2595,22 +2617,9 @@
for (int i=0; i<N; i++) {
ModeCallback cb = cbs.valueAt(i);
ArrayList<ChangeRec> reports = callbacks.get(cb);
- boolean duplicate = false;
- if (reports == null) {
- reports = new ArrayList<>();
- callbacks.put(cb, reports);
- } else {
- final int reportCount = reports.size();
- for (int j = 0; j < reportCount; j++) {
- ChangeRec report = reports.get(j);
- if (report.op == op && report.pkg.equals(packageName)) {
- duplicate = true;
- break;
- }
- }
- }
- if (!duplicate) {
- reports.add(new ChangeRec(op, uid, packageName));
+ ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName);
+ if (changed != reports) {
+ callbacks.put(cb, changed);
}
}
return callbacks;
@@ -2648,6 +2657,7 @@
enforceManageAppOpsModes(callingPid, callingUid, reqUid);
HashMap<ModeCallback, ArrayList<ChangeRec>> callbacks = null;
+ ArrayList<ChangeRec> allChanges = new ArrayList<>();
synchronized (this) {
boolean changed = false;
for (int i = mUidStates.size() - 1; i >= 0; i--) {
@@ -2668,6 +2678,9 @@
mOpModeWatchers.get(code));
callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
mPackageModeWatchers.get(packageName));
+
+ allChanges = addChange(allChanges, code, uidState.uid,
+ packageName);
}
}
}
@@ -2707,6 +2720,7 @@
callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
mPackageModeWatchers.get(packageName));
+ allChanges = addChange(allChanges, curOp.op, uid, packageName);
curOp.removeAttributionsWithNoTime();
if (curOp.mAttributions.isEmpty()) {
pkgOps.removeAt(j);
@@ -2741,6 +2755,15 @@
}
}
}
+
+ if (allChanges != null) {
+ int numChanges = allChanges.size();
+ for (int i = 0; i < numChanges; i++) {
+ ChangeRec change = allChanges.get(i);
+ notifyOpChangedSync(change.op, change.uid, change.pkg,
+ AppOpsManager.opToDefaultMode(change.op));
+ }
+ }
}
private void evalAllForegroundOpsLocked() {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index d3feeda..5328350 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -211,7 +211,13 @@
}
mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
} else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
- mForcedUseForComm = AudioSystem.FORCE_NONE;
+ if (mBtHelper.isBluetoothScoOn()) {
+ mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
+ setForceUse_Async(
+ AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource);
+ } else {
+ mForcedUseForComm = AudioSystem.FORCE_NONE;
+ }
}
mForcedUseForCommExt = mForcedUseForComm;
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 036f4f5..9137996 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -412,8 +412,15 @@
BluetoothHeadset.STATE_AUDIO_DISCONNECTED)) {
mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
- // startBluetoothSco called after stopBluetoothSco
- if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
+ // There are two cases where we want to immediately reconnect audio:
+ // 1) If a new start request was received while disconnecting: this was
+ // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
+ // 2) If audio was connected then disconnected via Bluetooth APIs and
+ // we still have pending activation requests by apps: this is indicated by
+ // state SCO_STATE_ACTIVE_EXTERNAL and the mScoClients list not empty.
+ if (mScoAudioState == SCO_STATE_ACTIVATE_REQ
+ || (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL
+ && !mScoClients.isEmpty())) {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
&& connectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, mScoAudioMode)) {
@@ -423,7 +430,9 @@
}
}
// Tear down SCO if disconnected from external
- clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
+ if (mScoAudioState == SCO_STATE_DEACTIVATING) {
+ clearAllScoClients(0, false);
+ }
mScoAudioState = SCO_STATE_INACTIVE;
Log.i(TAG, "Audio-path brought-down");
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 53f9ebc..1ed5cd8 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -2207,13 +2207,7 @@
@Override
public void setHdmiCecVolumeControlEnabled(final boolean isHdmiCecVolumeControlEnabled) {
enforceAccessPermission();
- runOnServiceThread(new Runnable() {
- @Override
- public void run() {
- HdmiControlService.this.setHdmiCecVolumeControlEnabled(
- isHdmiCecVolumeControlEnabled);
- }
- });
+ HdmiControlService.this.setHdmiCecVolumeControlEnabled(isHdmiCecVolumeControlEnabled);
}
@Override
@@ -3014,7 +3008,6 @@
}
void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {
- assertRunOnServiceThread();
synchronized (mLock) {
mHdmiCecVolumeControlEnabled = isHdmiCecVolumeControlEnabled;
@@ -3030,7 +3023,6 @@
}
boolean isHdmiCecVolumeControlEnabled() {
- assertRunOnServiceThread();
synchronized (mLock) {
return mHdmiCecVolumeControlEnabled;
}
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 8888108..c4f8441 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -41,9 +41,12 @@
import com.android.internal.BrightnessSynchronizer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.SystemService;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -174,6 +177,36 @@
closeSessionInternal(token);
}
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
+
+ synchronized (LightsService.this) {
+ if (mVintfLights != null) {
+ pw.println("Service: aidl (" + mVintfLights.get() + ")");
+ } else {
+ pw.println("Service: hidl");
+ }
+
+ pw.println("Lights:");
+ for (int i = 0; i < mLightsById.size(); i++) {
+ final LightImpl light = mLightsById.valueAt(i);
+ pw.println(String.format(" Light id=%d ordinal=%d color=%08x",
+ light.mHwLight.id, light.mHwLight.ordinal, light.getColor()));
+ }
+
+ pw.println("Session clients:");
+ for (Session session : mSessions) {
+ pw.println(" Session token=" + session.mToken);
+ for (int i = 0; i < session.mRequests.size(); i++) {
+ pw.println(String.format(" Request id=%d color=%08x",
+ session.mRequests.keyAt(i),
+ session.mRequests.valueAt(i).getColor()));
+ }
+ }
+ }
+ }
+
private void closeSessionInternal(IBinder token) {
synchronized (LightsService.this) {
final Session session = getSessionLocked(token);
diff --git a/services/core/java/com/android/server/lights/TEST_MAPPING b/services/core/java/com/android/server/lights/TEST_MAPPING
new file mode 100644
index 0000000..f868ea0
--- /dev/null
+++ b/services/core/java/com/android/server/lights/TEST_MAPPING
@@ -0,0 +1,21 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsHardwareTestCases",
+ "options": [
+ {"include-filter": "com.android.hardware.lights"},
+ {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.LargeTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ ]
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.lights"},
+ {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8f5b07c..bcfa6fc 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -314,7 +314,7 @@
}
void register(LockSettingsStrongAuth strongAuth) {
- strongAuth.registerStrongAuthTracker(this.mStub);
+ strongAuth.registerStrongAuthTracker(getStub());
}
}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 9e509f4..1a749b3 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -22,6 +22,7 @@
import android.app.IProcessObserver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
@@ -43,6 +44,7 @@
import android.util.ArrayMap;
import android.util.Slog;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -399,11 +401,12 @@
public final UserHandle userHandle;
private final int mTargetSdkVersion;
private final boolean mIsPrivileged;
+ private final int mType;
private IMediaProjectionCallback mCallback;
private IBinder mToken;
private IBinder.DeathRecipient mDeathEater;
- private int mType;
+ private boolean mRestoreSystemAlertWindow;
MediaProjection(int type, int uid, String packageName, int targetSdkVersion,
boolean isPrivileged) {
@@ -494,6 +497,35 @@
"MediaProjectionCallbacks must be valid, aborting MediaProjection", e);
return;
}
+ if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // We allow an app running a current screen capture session to use
+ // SYSTEM_ALERT_WINDOW for the duration of the session, to enable
+ // them to overlay their UX on top of what is being captured.
+ // We only do this if the app requests the permission, and the appop
+ // is in its default state (the user has neither explicitly allowed nor
+ // disallowed it).
+ final PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(
+ packageName, PackageManager.GET_PERMISSIONS,
+ UserHandle.getUserId(uid));
+ if (ArrayUtils.contains(packageInfo.requestedPermissions,
+ Manifest.permission.SYSTEM_ALERT_WINDOW)) {
+ final int currentMode = mAppOps.unsafeCheckOpRawNoThrow(
+ AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName);
+ if (currentMode == AppOpsManager.MODE_DEFAULT) {
+ mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid,
+ packageName, AppOpsManager.MODE_ALLOWED);
+ mRestoreSystemAlertWindow = true;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Package not found, aborting MediaProjection", e);
+ return;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
startProjectionLocked(this);
}
}
@@ -507,6 +539,24 @@
+ "pid=" + Binder.getCallingPid() + ")");
return;
}
+ if (mRestoreSystemAlertWindow) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Put the appop back how it was, unless it has been changed from what
+ // we set it to.
+ // Note that WindowManager takes care of removing any existing overlay
+ // windows when we do this.
+ final int currentMode = mAppOps.unsafeCheckOpRawNoThrow(
+ AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName);
+ if (currentMode == AppOpsManager.MODE_ALLOWED) {
+ mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, packageName,
+ AppOpsManager.MODE_DEFAULT);
+ }
+ mRestoreSystemAlertWindow = false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
stopProjectionLocked(this);
mToken.unlinkToDeath(mDeathEater, 0);
mToken = null;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ecd56d3..5106be9 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -935,6 +935,21 @@
}
@Override
+ public void uninstallExistingPackage(VersionedPackage versionedPackage,
+ String callerPackageName, IntentSender statusReceiver, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES, null);
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
+ if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
+ mAppOps.checkPackage(callingUid, callerPackageName);
+ }
+
+ final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
+ statusReceiver, versionedPackage.getPackageName(), false, userId);
+ mPm.deleteExistingPackageAsUser(versionedPackage, adapter.getBinder(), userId);
+ }
+
+ @Override
public void installExistingPackage(String packageName, int installFlags, int installReason,
IntentSender statusReceiver, int userId, List<String> whiteListedPermissions) {
mPm.installExistingPackageAsUser(packageName, userId, installFlags, installReason,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6bff5e9..4d7fb4e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -17982,8 +17982,46 @@
}
@Override
+ public void deleteExistingPackageAsUser(VersionedPackage versionedPackage,
+ final IPackageDeleteObserver2 observer, final int userId) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.DELETE_PACKAGES, null);
+ Preconditions.checkNotNull(versionedPackage);
+ Preconditions.checkNotNull(observer);
+ final String packageName = versionedPackage.getPackageName();
+ final long versionCode = versionedPackage.getLongVersionCode();
+
+ int installedForUsersCount = 0;
+ synchronized (mLock) {
+ // Normalize package name to handle renamed packages and static libs
+ final String internalPkgName = resolveInternalPackageNameLPr(packageName, versionCode);
+ final PackageSetting ps = mSettings.getPackageLPr(internalPkgName);
+ if (ps != null) {
+ int[] installedUsers = ps.queryInstalledUsers(mUserManager.getUserIds(), true);
+ installedForUsersCount = installedUsers.length;
+ }
+ }
+
+ if (installedForUsersCount > 1) {
+ deletePackageVersionedInternal(versionedPackage, observer, userId, 0, true);
+ } else {
+ try {
+ observer.onPackageDeleted(packageName, PackageManager.DELETE_FAILED_INTERNAL_ERROR,
+ null);
+ } catch (RemoteException re) {
+ }
+ }
+ }
+
+ @Override
public void deletePackageVersioned(VersionedPackage versionedPackage,
final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags) {
+ deletePackageVersionedInternal(versionedPackage, observer, userId, deleteFlags, false);
+ }
+
+ private void deletePackageVersionedInternal(VersionedPackage versionedPackage,
+ final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags,
+ final boolean allowSilentUninstall) {
final int callingUid = Binder.getCallingUid();
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DELETE_PACKAGES, null);
@@ -18004,6 +18042,7 @@
final int uid = Binder.getCallingUid();
if (!isOrphaned(internalPackageName)
+ && !allowSilentUninstall
&& !isCallerAllowedToSilentlyUninstall(uid, internalPackageName)) {
mHandler.post(() -> {
try {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index a1cc44a..7d49f78 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -421,7 +421,7 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mContext.getSystemService(PermissionControllerManager.class).dump(fd, pw, args);
+ mContext.getSystemService(PermissionControllerManager.class).dump(fd, args);
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c56fe2c..6e1bac1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1292,12 +1292,6 @@
}
if (stack != null && stack.topRunningActivity() == this) {
- // carry over the PictureInPictureParams to the parent stack without calling
- // TaskOrganizerController#dispatchTaskInfoChanged.
- // this is to ensure the stack holding up-to-dated pinned stack information
- // when activity is re-parented to enter pip mode, see also
- // RootWindowContainer#moveActivityToPinnedStack
- stack.mPictureInPictureParams.copyOnlySet(pictureInPictureArgs);
// make ensure the TaskOrganizer still works after re-parenting
if (firstWindowDrawn) {
stack.setHasBeenVisible(true);
@@ -7812,6 +7806,6 @@
void setPictureInPictureParams(PictureInPictureParams p) {
pictureInPictureArgs.copyOnlySet(p);
- getTask().getRootTask().setPictureInPictureParams(p);
+ getTask().getRootTask().onPictureInPictureParamsChanged();
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 30021d1..c165242 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -710,8 +710,10 @@
// Need to make sure windowing mode is supported. If we in the process of creating the stack
// no need to resolve the windowing mode again as it is already resolved to the right mode.
if (!creating) {
- windowingMode = taskDisplayArea.validateWindowingMode(windowingMode,
- null /* ActivityRecord */, topTask, getActivityType());
+ if (!taskDisplayArea.isValidWindowingMode(windowingMode, null /* ActivityRecord */,
+ topTask, getActivityType())) {
+ windowingMode = WINDOWING_MODE_UNDEFINED;
+ }
}
if (taskDisplayArea.getRootSplitScreenPrimaryTask() == this
&& windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 26a97be..8676e51 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -861,7 +861,7 @@
r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(),
r.getSavedState(), r.getPersistentSavedState(), results, newIntents,
dc.isNextTransitionForward(), proc.createProfilerInfoIfNeeded(),
- r.assistToken));
+ r.assistToken, r.createFixedRotationAdjustmentsIfNeeded()));
// Set desired final state.
final ActivityLifecycleItem lifecycleItem;
@@ -1473,6 +1473,7 @@
mService.deferWindowLayout();
try {
stack.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+ stack.setBounds(null);
if (toDisplay.getDisplayId() != stack.getDisplayId()) {
stack.reparent(toDisplay.getDefaultTaskDisplayArea(), false /* onTop */);
} else {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 317bb43..d02be88 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -60,7 +60,37 @@
private final IntArray mShowingTransientTypes = new IntArray();
/** For resetting visibilities of insets sources. */
- private final InsetsControlTarget mDummyControlTarget = new InsetsControlTarget() { };
+ private final InsetsControlTarget mDummyControlTarget = new InsetsControlTarget() {
+
+ @Override
+ public void notifyInsetsControlChanged() {
+ boolean hasLeash = false;
+ final InsetsSourceControl[] controls =
+ mStateController.getControlsForDispatch(this);
+ if (controls == null) {
+ return;
+ }
+ for (InsetsSourceControl control : controls) {
+ final @InternalInsetsType int type = control.getType();
+ if (mShowingTransientTypes.indexOf(type) != -1) {
+ // The visibilities of transient bars will be handled with animations.
+ continue;
+ }
+ final SurfaceControl leash = control.getLeash();
+ if (leash != null) {
+ hasLeash = true;
+
+ // We use alpha to control the visibility here which aligns the logic at
+ // SurfaceAnimator.createAnimationLeash
+ mDisplayContent.getPendingTransaction().setAlpha(
+ leash, InsetsState.getDefaultVisibility(type) ? 1f : 0f);
+ }
+ }
+ if (hasLeash) {
+ mDisplayContent.scheduleAnimation();
+ }
+ }
+ };
private WindowState mFocusedWin;
private BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 4cd3180..513be7a 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -146,7 +146,10 @@
if (mTmpParams.hasWindowingMode()
&& mTmpParams.mWindowingMode != task.getStack().getWindowingMode()) {
- task.getStack().setWindowingMode(mTmpParams.mWindowingMode);
+ final int activityType = activity != null
+ ? activity.getActivityType() : task.getActivityType();
+ task.getStack().setWindowingMode(task.getDisplayArea().validateWindowingMode(
+ mTmpParams.mWindowingMode, activity, task, activityType));
}
if (mTmpParams.mBounds.isEmpty()) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 640f50c..5efbc3e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2170,7 +2170,7 @@
final boolean singleActivity = task.getChildCount() == 1;
final ActivityStack stack;
if (singleActivity) {
- stack = r.getRootTask();
+ stack = (ActivityStack) task;
} else {
// In the case of multiple activities, we will create a new task for it and then
// move the PIP activity into the task.
@@ -2183,6 +2183,11 @@
// up-to-dated pinned stack information on this newly created stack.
r.reparent(stack, MAX_VALUE, reason);
}
+ if (stack.getParent() != taskDisplayArea) {
+ // stack is nested, but pinned tasks need to be direct children of their
+ // display area, so reparent.
+ stack.reparent(taskDisplayArea, true /* onTop */);
+ }
stack.setWindowingMode(WINDOWING_MODE_PINNED);
// Reset the state that indicates it can enter PiP while pausing after we've moved it
diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java
index c79cb04..3d305e4 100644
--- a/services/core/java/com/android/server/wm/SeamlessRotator.java
+++ b/services/core/java/com/android/server/wm/SeamlessRotator.java
@@ -45,11 +45,22 @@
private final float[] mFloat9 = new float[9];
private final int mOldRotation;
private final int mNewRotation;
+ /* If the seamless rotator is used to rotate part of the hierarchy, then provide a transform
+ * hint based on the display orientation if the entire display was rotated. When the display
+ * orientation matches the hierarchy orientation, the fixed transform hint will be removed.
+ * This will prevent allocating different buffer sizes by the graphic producers when the
+ * orientation of a layer changes.
+ */
+ private final boolean mApplyFixedTransformHint;
+ private final int mFixedTransformHint;
- public SeamlessRotator(@Rotation int oldRotation, @Rotation int newRotation, DisplayInfo info) {
+
+ public SeamlessRotator(@Rotation int oldRotation, @Rotation int newRotation, DisplayInfo info,
+ boolean applyFixedTransformationHint) {
mOldRotation = oldRotation;
mNewRotation = newRotation;
-
+ mApplyFixedTransformHint = applyFixedTransformationHint;
+ mFixedTransformHint = oldRotation;
final boolean flipped = info.rotation == ROTATION_90 || info.rotation == ROTATION_270;
final int pH = flipped ? info.logicalWidth : info.logicalHeight;
final int pW = flipped ? info.logicalHeight : info.logicalWidth;
@@ -70,6 +81,9 @@
final float[] winSurfacePos = {win.mLastSurfacePosition.x, win.mLastSurfacePosition.y};
mTransform.mapPoints(winSurfacePos);
transaction.setPosition(win.getSurfaceControl(), winSurfacePos[0], winSurfacePos[1]);
+ if (mApplyFixedTransformHint) {
+ transaction.setFixedTransformHint(win.mSurfaceControl, mFixedTransformHint);
+ }
}
/**
@@ -109,6 +123,9 @@
mTransform.reset();
t.setMatrix(win.mSurfaceControl, mTransform, mFloat9);
t.setPosition(win.mSurfaceControl, win.mLastSurfacePosition.x, win.mLastSurfacePosition.y);
+ if (mApplyFixedTransformHint) {
+ t.unsetFixedTransformHint(win.mSurfaceControl);
+ }
}
public void dump(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4845da1..b9e6513 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -107,7 +107,6 @@
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.AppGlobals;
-import android.app.PictureInPictureParams;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.ComponentName;
@@ -488,12 +487,6 @@
boolean mTaskAppearedSent;
/**
- * Last Picture-in-Picture params applicable to the task. Updated when the app
- * enters Picture-in-Picture or when setPictureInPictureParams is called.
- */
- PictureInPictureParams mPictureInPictureParams = new PictureInPictureParams.Builder().build();
-
- /**
* This task was created by the task organizer which has the following implementations.
* <ul>
* <lis>The task won't be removed when it is empty. Removal has to be an explicit request
@@ -3571,10 +3564,11 @@
info.resizeMode = top != null ? top.mResizeMode : mResizeMode;
info.topActivityType = top.getActivityType();
- if (mPictureInPictureParams.empty()) {
+ ActivityRecord rootActivity = top.getRootActivity();
+ if (rootActivity == null || rootActivity.pictureInPictureArgs.empty()) {
info.pictureInPictureParams = null;
} else {
- info.pictureInPictureParams = mPictureInPictureParams;
+ info.pictureInPictureParams = rootActivity.pictureInPictureArgs;
}
info.topActivityInfo = mReuseActivitiesReport.top != null
? mReuseActivitiesReport.top.info
@@ -4490,8 +4484,7 @@
updateShadowsRadius(hasFocus, getPendingTransaction());
}
- void setPictureInPictureParams(PictureInPictureParams p) {
- mPictureInPictureParams.copyOnlySet(p);
+ void onPictureInPictureParamsChanged() {
if (isOrganized()) {
mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, true /* force */);
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 0b64451..1225714 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -21,7 +21,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -1389,16 +1388,16 @@
}
/**
- * Check that the requested windowing-mode is appropriate for the specified task and/or activity
+ * Check if the requested windowing-mode is appropriate for the specified task and/or activity
* on this display.
*
* @param windowingMode The windowing-mode to validate.
* @param r The {@link ActivityRecord} to check against.
* @param task The {@link Task} to check against.
* @param activityType An activity type.
- * @return The provided windowingMode or the closest valid mode which is appropriate.
+ * @return {@code true} if windowingMode is valid, {@code false} otherwise.
*/
- int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
+ boolean isValidWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
int activityType) {
// Make sure the windowing mode we are trying to use makes sense for what is supported.
boolean supportsMultiWindow = mAtmService.mSupportsMultiWindow;
@@ -1418,24 +1417,35 @@
}
}
+ return windowingMode != WINDOWING_MODE_UNDEFINED
+ && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+ supportsFreeform, supportsPip, activityType);
+ }
+
+ /**
+ * Check that the requested windowing-mode is appropriate for the specified task and/or activity
+ * on this display.
+ *
+ * @param windowingMode The windowing-mode to validate.
+ * @param r The {@link ActivityRecord} to check against.
+ * @param task The {@link Task} to check against.
+ * @param activityType An activity type.
+ * @return The provided windowingMode or the closest valid mode which is appropriate.
+ */
+ int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
+ int activityType) {
final boolean inSplitScreenMode = isSplitScreenModeActivated();
- if (!inSplitScreenMode
- && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+ if (!inSplitScreenMode && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
// Switch to the display's windowing mode if we are not in split-screen mode and we are
// trying to launch in split-screen secondary.
windowingMode = WINDOWING_MODE_UNDEFINED;
- } else if (inSplitScreenMode && (windowingMode == WINDOWING_MODE_FULLSCREEN
- || windowingMode == WINDOWING_MODE_UNDEFINED)
- && supportsSplitScreen) {
+ } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_UNDEFINED) {
windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
}
-
- if (windowingMode != WINDOWING_MODE_UNDEFINED
- && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
- supportsFreeform, supportsPip, activityType)) {
- return windowingMode;
+ if (!isValidWindowingMode(windowingMode, r, task, activityType)) {
+ return WINDOWING_MODE_UNDEFINED;
}
- return WINDOWING_MODE_UNDEFINED;
+ return windowingMode;
}
boolean isTopStack(ActivityStack stack) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8a47109..2dbc6b5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2263,7 +2263,7 @@
win.mRelayoutCalled = true;
win.mInRelayout = true;
- win.mViewVisibility = viewVisibility;
+ win.setViewVisibility(viewVisibility);
ProtoLog.i(WM_DEBUG_SCREEN_ON,
"Relayout %s: oldVis=%d newVis=%d. %s", win, oldVisibility,
viewVisibility, new RuntimeException().fillInStackTrace());
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f6473fd..e925ce5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -741,7 +741,8 @@
if (mControllableInsetProvider != null) {
mControllableInsetProvider.startSeamlessRotation();
}
- mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo());
+ mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo(),
+ false /* applyFixedTransformationHint */);
mPendingSeamlessRotate.unrotate(transaction, this);
getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
true /* seamlesslyRotated */);
@@ -5688,6 +5689,17 @@
return mSession.mPid == pid && isNonToastOrStarting() && isVisibleNow();
}
+ void setViewVisibility(int viewVisibility) {
+ mViewVisibility = viewVisibility;
+ // The viewVisibility is set to GONE with a client request to relayout. If this occurs and
+ // there's a blast sync transaction waiting, finishDrawing will never be called since the
+ // client will not render when visibility is GONE. Therefore, call finishDrawing here to
+ // prevent system server from blocking on a window that will not draw.
+ if (viewVisibility == View.GONE && mUsingBLASTSyncTransaction) {
+ finishDrawing(null);
+ }
+ }
+
SurfaceControl getClientViewRootSurface() {
return mWinAnimator.getClientViewRootSurface();
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 148264a..768f89e 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -19,6 +19,7 @@
import static android.os.Process.INVALID_UID;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
@@ -40,6 +41,7 @@
import android.annotation.CallSuper;
import android.app.IWindowToken;
+import android.app.servertransaction.FixedRotationAdjustmentsItem;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Debug;
@@ -47,6 +49,7 @@
import android.os.RemoteException;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import android.view.DisplayAdjustments.FixedRotationAdjustments;
import android.view.DisplayInfo;
import android.view.InsetsState;
import android.view.SurfaceControl;
@@ -141,7 +144,7 @@
mRotatedOverrideConfiguration = rotatedConfig;
// This will use unrotate as rotate, so the new and old rotation are inverted.
mRotator = new SeamlessRotator(rotatedDisplayInfo.rotation, currentRotation,
- rotatedDisplayInfo);
+ rotatedDisplayInfo, true /* applyFixedTransformationHint */);
}
/**
@@ -529,6 +532,7 @@
mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames,
insetsState, new Configuration(config), mDisplayContent.getRotation());
onConfigurationChanged(getParent().getConfiguration());
+ notifyFixedRotationTransform(true /* enabled */);
}
/**
@@ -546,6 +550,7 @@
mFixedRotationTransformState = fixedRotationState;
fixedRotationState.mAssociatedTokens.add(this);
onConfigurationChanged(getParent().getConfiguration());
+ notifyFixedRotationTransform(true /* enabled */);
}
void finishFixedRotationTransform() {
@@ -578,9 +583,52 @@
// The state is cleared at the end, because it is used to indicate that other windows can
// use seamless rotation when applying rotation to display.
for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) {
- state.mAssociatedTokens.get(i).mFixedRotationTransformState = null;
+ state.mAssociatedTokens.get(i).cleanUpFixedRotationTransformState();
}
+ cleanUpFixedRotationTransformState();
+ }
+
+ private void cleanUpFixedRotationTransformState() {
mFixedRotationTransformState = null;
+ notifyFixedRotationTransform(false /* enabled */);
+ }
+
+ /** Notifies application side to enable or disable the rotation adjustment of display info. */
+ private void notifyFixedRotationTransform(boolean enabled) {
+ FixedRotationAdjustments adjustments = null;
+ // A token may contain windows of the same processes or different processes. The list is
+ // used to avoid sending the same adjustments to a process multiple times.
+ ArrayList<WindowProcessController> notifiedProcesses = null;
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final WindowState w = mChildren.get(i);
+ final WindowProcessController app;
+ if (w.mAttrs.type == TYPE_APPLICATION_STARTING) {
+ // Use the host activity because starting window is controlled by window manager.
+ final ActivityRecord r = asActivityRecord();
+ if (r == null) {
+ continue;
+ }
+ app = r.app;
+ } else {
+ app = mWmService.mAtmService.mProcessMap.getProcess(w.mSession.mPid);
+ }
+ if (app == null || !app.hasThread()) {
+ continue;
+ }
+ if (notifiedProcesses == null) {
+ notifiedProcesses = new ArrayList<>(2);
+ adjustments = enabled ? createFixedRotationAdjustmentsIfNeeded() : null;
+ } else if (notifiedProcesses.contains(app)) {
+ continue;
+ }
+ notifiedProcesses.add(app);
+ try {
+ mWmService.mAtmService.getLifecycleManager().scheduleTransaction(
+ app.getThread(), FixedRotationAdjustmentsItem.obtain(token, adjustments));
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to schedule DisplayAdjustmentsItem to " + app, e);
+ }
+ }
}
/** Restores the changes that applies to this container. */
@@ -590,6 +638,7 @@
// The window may be detached or detaching.
return;
}
+ notifyFixedRotationTransform(false /* enabled */);
final int originalRotation = getWindowConfiguration().getRotation();
onConfigurationChanged(parent.getConfiguration());
onCancelFixedRotationTransform(originalRotation);
@@ -603,6 +652,14 @@
void onCancelFixedRotationTransform(int originalDisplayRotation) {
}
+ FixedRotationAdjustments createFixedRotationAdjustmentsIfNeeded() {
+ if (!isFixedRotationTransforming()) {
+ return null;
+ }
+ return new FixedRotationAdjustments(mFixedRotationTransformState.mDisplayInfo.rotation,
+ mFixedRotationTransformState.mDisplayInfo.displayCutout);
+ }
+
@Override
void resolveOverrideConfiguration(Configuration newParentConfig) {
super.resolveOverrideConfiguration(newParentConfig);
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
new file mode 100644
index 0000000..8646a53
--- /dev/null
+++ b/services/core/jni/OWNERS
@@ -0,0 +1,13 @@
+# Display
+per-file com_android_server_lights_LightsService.cpp = michaelwr@google.com, santoscordon@google.com
+
+# Haptics
+per-file com_android_server_VibratorService.cpp = michaelwr@google.com
+
+# Input
+per-file com_android_server_input_InputManagerService.cpp = michaelwr@google.com, svv@google.com
+
+# Power
+per-file com_android_server_HardwarePropertiesManagerService.cpp = michaelwr@google.com, santoscordon@google.com
+per-file com_android_server_power_PowerManagerService.* = michaelwr@google.com, santoscordon@google.com
+
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index da716ea..c687184 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -30,6 +30,7 @@
import android.os.IBinder;
import android.os.ServiceManager;
import android.provider.Settings;
+import android.provider.Telephony;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
@@ -84,6 +85,7 @@
result.removeAll(getSystemLauncherPackages());
result.removeAll(getAccessibilityServices());
result.removeAll(getInputMethodPackages());
+ result.remove(Telephony.Sms.getDefaultSmsPackage(mContext));
result.remove(getSettingsPackageName());
final String[] unsuspendablePackages =
diff --git a/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java b/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java
index 5d849c1..2be3f1e8 100644
--- a/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java
@@ -19,6 +19,7 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
import android.app.usage.UsageEvents;
import android.content.res.Configuration;
@@ -26,9 +27,12 @@
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.ArrayUtils;
+
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.lang.reflect.Field;
import java.util.Locale;
@RunWith(AndroidJUnit4.class)
@@ -191,4 +195,27 @@
assertTrue(intervalStats.events.size() > NUMBER_OF_EVENTS - NUMBER_OF_EVENTS_PER_PACKAGE);
assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES);
}
+
+ // All fields in this list are defined in IntervalStats and persisted - please ensure they're
+ // defined correctly in both usagestatsservice.proto and usagestatsservice_v2.proto
+ private static final String[] INTERVALSTATS_PERSISTED_FIELDS = {"beginTime", "endTime",
+ "mStringCache", "majorVersion", "minorVersion", "interactiveTracker",
+ "nonInteractiveTracker", "keyguardShownTracker", "keyguardHiddenTracker",
+ "packageStats", "configurations", "activeConfiguration", "events"};
+ // All fields in this list are defined in IntervalStats but not persisted
+ private static final String[] INTERVALSTATS_IGNORED_FIELDS = {"lastTimeSaved",
+ "packageStatsObfuscated", "CURRENT_MAJOR_VERSION", "CURRENT_MINOR_VERSION", "TAG"};
+
+ @Test
+ public void testIntervalStatsFieldsAreKnown() {
+ final IntervalStats stats = new IntervalStats();
+ final Field[] fields = stats.getClass().getDeclaredFields();
+ for (Field field : fields) {
+ if (!(ArrayUtils.contains(INTERVALSTATS_PERSISTED_FIELDS, field.getName())
+ || ArrayUtils.contains(INTERVALSTATS_IGNORED_FIELDS, field.getName()))) {
+ fail("Found an unknown field: " + field.getName() + ". Please correctly update "
+ + "either INTERVALSTATS_PERSISTED_FIELDS or INTERVALSTATS_IGNORED_FIELDS.");
+ }
+ }
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index fc256b0..702d9d3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -53,6 +53,7 @@
import com.android.server.am.ActivityManagerService;
import com.android.server.pm.PackageManagerService;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -161,6 +162,12 @@
mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME;
}
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ }
+
@Test
public void testSuspendedByAdminPackage() {
// GIVEN the package we're about to launch is currently suspended
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index 851b052..d7eedd9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import android.annotation.NonNull;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.Region;
@@ -265,4 +266,15 @@
public SurfaceControl.Transaction setShadowRadius(SurfaceControl sc, float shadowRadius) {
return this;
}
+
+ @Override
+ public SurfaceControl.Transaction setFixedTransformHint(SurfaceControl sc,
+ @Surface.Rotation int transformHint) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) {
+ return this;
+ }
}
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 2ea58a0..fdc5c7b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -836,7 +836,7 @@
spyOn(record);
doReturn(true).when(record).checkEnterPictureInPictureState(any(), anyBoolean());
- record.getRootTask().setHasBeenVisible(true);
+ record.getTask().setHasBeenVisible(true);
return record;
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsIdleService.java b/services/usage/java/com/android/server/usage/UsageStatsIdleService.java
index 4468871..3163820 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsIdleService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsIdleService.java
@@ -27,6 +27,8 @@
import com.android.server.LocalServices;
+import java.util.concurrent.TimeUnit;
+
/**
* JobService used to do any work for UsageStats while the device is idle.
*/
@@ -36,6 +38,11 @@
* Base job ID for the pruning job - must be unique within the system server uid.
*/
private static final int PRUNE_JOB_ID = 546357475;
+ /**
+ * Job ID for the update mappings job - must be unique within the system server uid.
+ * Incrementing PRUNE_JOB_ID by 21475 (MAX_USER_ID) to ensure there is no overlap in job ids.
+ */
+ private static final int UPDATE_MAPPINGS_JOB_ID = 546378950;
private static final String USER_ID_KEY = "user_id";
@@ -51,35 +58,65 @@
.setPersisted(true)
.build();
+ scheduleJobInternal(context, pruneJob, userJobId);
+ }
+
+ static void scheduleUpdateMappingsJob(Context context) {
+ final ComponentName component = new ComponentName(context.getPackageName(),
+ UsageStatsIdleService.class.getName());
+ final JobInfo updateMappingsJob = new JobInfo.Builder(UPDATE_MAPPINGS_JOB_ID, component)
+ .setPersisted(true)
+ .setMinimumLatency(TimeUnit.DAYS.toMillis(1))
+ .setOverrideDeadline(TimeUnit.DAYS.toMillis(2))
+ .build();
+
+ scheduleJobInternal(context, updateMappingsJob, UPDATE_MAPPINGS_JOB_ID);
+ }
+
+ private static void scheduleJobInternal(Context context, JobInfo pruneJob, int jobId) {
final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
- final JobInfo pendingPruneJob = jobScheduler.getPendingJob(userJobId);
+ final JobInfo pendingPruneJob = jobScheduler.getPendingJob(jobId);
// only schedule a new prune job if one doesn't exist already for this user
if (!pruneJob.equals(pendingPruneJob)) {
- jobScheduler.cancel(userJobId); // cancel any previously scheduled prune job
+ jobScheduler.cancel(jobId); // cancel any previously scheduled prune job
jobScheduler.schedule(pruneJob);
}
-
}
static void cancelJob(Context context, int userId) {
- final int userJobId = PRUNE_JOB_ID + userId; // unique job id per user
+ cancelJobInternal(context, PRUNE_JOB_ID + userId);
+ }
+
+ static void cancelUpdateMappingsJob(Context context) {
+ cancelJobInternal(context, UPDATE_MAPPINGS_JOB_ID);
+ }
+
+ private static void cancelJobInternal(Context context, int jobId) {
final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
- jobScheduler.cancel(userJobId);
+ if (jobScheduler != null) {
+ jobScheduler.cancel(jobId);
+ }
}
@Override
public boolean onStartJob(JobParameters params) {
final PersistableBundle bundle = params.getExtras();
final int userId = bundle.getInt(USER_ID_KEY, -1);
- if (userId == -1) {
+ if (userId == -1 && params.getJobId() != UPDATE_MAPPINGS_JOB_ID) {
return false;
}
AsyncTask.execute(() -> {
final UsageStatsManagerInternal usageStatsManagerInternal = LocalServices.getService(
UsageStatsManagerInternal.class);
- final boolean pruned = usageStatsManagerInternal.pruneUninstalledPackagesData(userId);
- jobFinished(params, !pruned); // reschedule if data was not pruned
+ if (params.getJobId() == UPDATE_MAPPINGS_JOB_ID) {
+ final boolean jobFinished = usageStatsManagerInternal.updatePackageMappingsData();
+ jobFinished(params, !jobFinished); // reschedule if data was not updated
+ } else {
+ final boolean jobFinished =
+ usageStatsManagerInternal.pruneUninstalledPackagesData(userId);
+ jobFinished(params, !jobFinished); // reschedule if data was not pruned
+ }
});
return true;
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 5b5d57b..b59556f 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -332,6 +332,11 @@
private void onUserUnlocked(int userId) {
// fetch the installed packages outside the lock so it doesn't block package manager.
final HashMap<String, Long> installedPackages = getInstalledPackages(userId);
+ // delay updating of package mappings for user 0 since their data is not likely to be stale.
+ // this also makes it less likely for restored data to be erased on unexpected reboots.
+ if (userId == UserHandle.USER_SYSTEM) {
+ UsageStatsIdleService.scheduleUpdateMappingsJob(getContext());
+ }
synchronized (mLock) {
// Create a user unlocked event to report
final Event unlockEvent = new Event(USER_UNLOCKED, SystemClock.elapsedRealtime());
@@ -543,8 +548,8 @@
* Initializes the given user's usage stats service - this should ideally only be called once,
* when the user is initially unlocked.
*/
- private void initializeUserUsageStatsServiceLocked(int userId,
- long currentTimeMillis, HashMap<String, Long> installedPackages) {
+ private void initializeUserUsageStatsServiceLocked(int userId, long currentTimeMillis,
+ HashMap<String, Long> installedPackages) {
final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId),
"usagestats");
final UserUsageStatsService service = new UserUsageStatsService(getContext(), userId,
@@ -931,6 +936,7 @@
}
// Cancel any scheduled jobs for this user since the user is being removed.
UsageStatsIdleService.cancelJob(getContext(), userId);
+ UsageStatsIdleService.cancelUpdateMappingsJob(getContext());
}
/**
@@ -980,6 +986,26 @@
/**
* Called by the Binder stub.
*/
+ private boolean updatePackageMappingsData() {
+ // fetch the installed packages outside the lock so it doesn't block package manager.
+ final HashMap<String, Long> installedPkgs = getInstalledPackages(UserHandle.USER_SYSTEM);
+ synchronized (mLock) {
+ if (!mUserUnlockedStates.get(UserHandle.USER_SYSTEM)) {
+ return false; // user is no longer unlocked
+ }
+
+ final UserUsageStatsService userService = mUserState.get(UserHandle.USER_SYSTEM);
+ if (userService == null) {
+ return false; // user was stopped or removed
+ }
+
+ return userService.updatePackageMappingsLocked(installedPkgs);
+ }
+ }
+
+ /**
+ * Called by the Binder stub.
+ */
List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime,
boolean obfuscateInstantApps) {
synchronized (mLock) {
@@ -1847,7 +1873,7 @@
final DevicePolicyManagerInternal dpmInternal = getDpmInternal();
if (!hasPermissions(callingPackage,
Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)
- && (dpmInternal != null && !dpmInternal.isActiveSupervisionApp(callingUid))) {
+ && (dpmInternal == null || !dpmInternal.isActiveSupervisionApp(callingUid))) {
throw new SecurityException("Caller must be the active supervision app or "
+ "it must have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions");
}
@@ -1874,7 +1900,7 @@
final DevicePolicyManagerInternal dpmInternal = getDpmInternal();
if (!hasPermissions(callingPackage,
Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)
- && (dpmInternal != null && !dpmInternal.isActiveSupervisionApp(callingUid))) {
+ && (dpmInternal == null || !dpmInternal.isActiveSupervisionApp(callingUid))) {
throw new SecurityException("Caller must be the active supervision app or "
+ "it must have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions");
}
@@ -2137,6 +2163,9 @@
}
// Check to ensure that only user 0's data is b/r for now
+ // Note: if backup and restore is enabled for users other than the system user, the
+ // #onUserUnlocked logic, specifically when the update mappings job is scheduled via
+ // UsageStatsIdleService.scheduleUpdateMappingsJob, will have to be updated.
if (user == UserHandle.USER_SYSTEM) {
final UserUsageStatsService userStats = getUserUsageStatsServiceLocked(user);
if (userStats == null) {
@@ -2229,6 +2258,11 @@
public boolean pruneUninstalledPackagesData(int userId) {
return UsageStatsService.this.pruneUninstalledPackagesData(userId);
}
+
+ @Override
+ public boolean updatePackageMappingsData() {
+ return UsageStatsService.this.updatePackageMappingsData();
+ }
}
private class MyPackageMonitor extends PackageMonitor {
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 4e75b73..26de11a 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -40,6 +40,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -181,19 +182,27 @@
private void readPackageMappingsLocked(HashMap<String, Long> installedPackages) {
mDatabase.readMappingsLocked();
- updatePackageMappingsLocked(installedPackages);
+ // Package mappings for the system user are updated after 24 hours via a job scheduled by
+ // UsageStatsIdleService to ensure restored data is not lost on first boot. Additionally,
+ // this makes user service initialization a little quicker on subsequent boots.
+ if (mUserId != UserHandle.USER_SYSTEM) {
+ updatePackageMappingsLocked(installedPackages);
+ }
}
/**
- * Queries Job Scheduler for any pending data prune jobs and if any exist, it updates the
- * package mappings in memory by removing those tokens.
+ * Compares the package mappings on disk with the ones currently installed and removes the
+ * mappings for those packages that have been uninstalled.
* This will only happen once per device boot, when the user is unlocked for the first time.
+ * If the user is the system user (user 0), this is delayed to ensure data for packages
+ * that were restored isn't removed before the restore is complete.
*
* @param installedPackages map of installed packages (package_name:package_install_time)
+ * @return {@code true} on a successful mappings update, {@code false} otherwise.
*/
- private void updatePackageMappingsLocked(HashMap<String, Long> installedPackages) {
+ boolean updatePackageMappingsLocked(HashMap<String, Long> installedPackages) {
if (ArrayUtils.isEmpty(installedPackages)) {
- return;
+ return true;
}
final long timeNow = System.currentTimeMillis();
@@ -206,7 +215,7 @@
}
}
if (removedPackages.isEmpty()) {
- return;
+ return true;
}
// remove packages in the mappings that are no longer installed and persist to disk
@@ -217,7 +226,9 @@
mDatabase.writeMappingsLocked();
} catch (Exception e) {
Slog.w(TAG, "Unable to write updated package mappings file on service initialization.");
+ return false;
}
+ return true;
}
boolean pruneUninstalledPackagesData() {
diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
index ff70f8b..29286e8 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
@@ -240,5 +240,5 @@
* two.
* Type: int
*/
- static final String PROPERTY_MAX_ACTIVE_MODEMS = "ro.telephony.max.active.modems";
+ static final String PROPERTY_MAX_ACTIVE_MODEMS = "telephony.active_modems.max_count";
}
diff --git a/test-mock/api/lint-baseline.txt b/test-mock/api/lint-baseline.txt
index c6ba3f5..1411824 100644
--- a/test-mock/api/lint-baseline.txt
+++ b/test-mock/api/lint-baseline.txt
@@ -21,10 +21,6 @@
Missing nullability on parameter `url` in method `getStreamTypes`
MissingNullability: android.test.mock.MockContentProvider#getStreamTypes(android.net.Uri, String) parameter #1:
Missing nullability on parameter `mimeTypeFilter` in method `getStreamTypes`
-MissingNullability: android.test.mock.MockContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean) parameter #0:
- Missing nullability on parameter `uri` in method `notifyChange`
-MissingNullability: android.test.mock.MockContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean) parameter #1:
- Missing nullability on parameter `observer` in method `notifyChange`
MissingNullability: android.test.mock.MockContext#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection) parameter #0:
Missing nullability on parameter `service` in method `bindIsolatedService`
MissingNullability: android.test.mock.MockContext#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection) parameter #2:
@@ -39,6 +35,10 @@
Missing nullability on parameter `executor` in method `bindService`
MissingNullability: android.test.mock.MockContext#bindService(android.content.Intent, int, java.util.concurrent.Executor, android.content.ServiceConnection) parameter #3:
Missing nullability on parameter `conn` in method `bindService`
+MissingNullability: android.test.mock.MockContext#createWindowContext(int, android.os.Bundle) parameter #1:
+ Missing nullability on parameter `options` in method `createWindowContext`
+MissingNullability: android.test.mock.MockContext#getDisplay():
+ Missing nullability on method `getDisplay` return
MissingNullability: android.test.mock.MockContext#getMainExecutor():
Missing nullability on method `getMainExecutor` return
MissingNullability: android.test.mock.MockContext#sendOrderedBroadcast(android.content.Intent, String, String, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle) parameter #0:
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 23098ec..529d03c 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -547,6 +547,16 @@
@Test
public void testApplyTransportModeTransform() throws Exception {
+ verifyApplyTransportModeTransformCommon(false);
+ }
+
+ @Test
+ public void testApplyTransportModeTransformReleasedSpi() throws Exception {
+ verifyApplyTransportModeTransformCommon(true);
+ }
+
+ public void verifyApplyTransportModeTransformCommon(
+ boolean closeSpiBeforeApply) throws Exception {
IpSecConfig ipSecConfig = new IpSecConfig();
addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
addAuthAndCryptToIpSecConfig(ipSecConfig);
@@ -554,6 +564,39 @@
IpSecTransformResponse createTransformResp =
mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ if (closeSpiBeforeApply) {
+ mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
+ }
+
+ Socket socket = new Socket();
+ socket.bind(null);
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket);
+
+ int resourceId = createTransformResp.resourceId;
+ mIpSecService.applyTransportModeTransform(pfd, IpSecManager.DIRECTION_OUT, resourceId);
+
+ verify(mMockNetd)
+ .ipSecApplyTransportModeTransform(
+ eq(pfd),
+ eq(mUid),
+ eq(IpSecManager.DIRECTION_OUT),
+ anyString(),
+ anyString(),
+ eq(TEST_SPI));
+ }
+
+ @Test
+ public void testApplyTransportModeTransformWithClosedSpi() throws Exception {
+ IpSecConfig ipSecConfig = new IpSecConfig();
+ addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+ addAuthAndCryptToIpSecConfig(ipSecConfig);
+
+ IpSecTransformResponse createTransformResp =
+ mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+
+ // Close SPI record
+ mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
+
Socket socket = new Socket();
socket.bind(null);
ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket);
@@ -660,6 +703,15 @@
@Test
public void testApplyTunnelModeTransform() throws Exception {
+ verifyApplyTunnelModeTransformCommon(false);
+ }
+
+ @Test
+ public void testApplyTunnelModeTransformReleasedSpi() throws Exception {
+ verifyApplyTunnelModeTransformCommon(true);
+ }
+
+ public void verifyApplyTunnelModeTransformCommon(boolean closeSpiBeforeApply) throws Exception {
IpSecConfig ipSecConfig = new IpSecConfig();
ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL);
addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
@@ -670,6 +722,49 @@
IpSecTunnelInterfaceResponse createTunnelResp =
createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+ if (closeSpiBeforeApply) {
+ mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
+ }
+
+ int transformResourceId = createTransformResp.resourceId;
+ int tunnelResourceId = createTunnelResp.resourceId;
+ mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
+ transformResourceId, "blessedPackage");
+
+ for (int selAddrFamily : ADDRESS_FAMILIES) {
+ verify(mMockNetd)
+ .ipSecUpdateSecurityPolicy(
+ eq(mUid),
+ eq(selAddrFamily),
+ eq(IpSecManager.DIRECTION_OUT),
+ anyString(),
+ anyString(),
+ eq(TEST_SPI),
+ anyInt(), // iKey/oKey
+ anyInt(), // mask
+ eq(tunnelResourceId));
+ }
+
+ ipSecConfig.setXfrmInterfaceId(tunnelResourceId);
+ verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
+ }
+
+
+ @Test
+ public void testApplyTunnelModeTransformWithClosedSpi() throws Exception {
+ IpSecConfig ipSecConfig = new IpSecConfig();
+ ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL);
+ addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+ addAuthAndCryptToIpSecConfig(ipSecConfig);
+
+ IpSecTransformResponse createTransformResp =
+ mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ IpSecTunnelInterfaceResponse createTunnelResp =
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+
+ // Close SPI record
+ mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
+
int transformResourceId = createTransformResp.resourceId;
int tunnelResourceId = createTunnelResp.resourceId;
mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
diff --git a/tools/validatekeymaps/OWNERS b/tools/validatekeymaps/OWNERS
new file mode 100644
index 0000000..0313a40
--- /dev/null
+++ b/tools/validatekeymaps/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+svv@google.com
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 6dd6864..30bc6a7 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -529,6 +529,161 @@
* {@hide}
*/
public final static int UNSPECIFIED = -1;
+
+ /**
+ * 2.4 GHz band first channel number
+ * @hide
+ */
+ public static final int BAND_24_GHZ_FIRST_CH_NUM = 1;
+ /**
+ * 2.4 GHz band last channel number
+ * @hide
+ */
+ public static final int BAND_24_GHZ_LAST_CH_NUM = 14;
+ /**
+ * 2.4 GHz band frequency of first channel in MHz
+ * @hide
+ */
+ public static final int BAND_24_GHZ_START_FREQ_MHZ = 2412;
+ /**
+ * 2.4 GHz band frequency of last channel in MHz
+ * @hide
+ */
+ public static final int BAND_24_GHZ_END_FREQ_MHZ = 2484;
+
+ /**
+ * 5 GHz band first channel number
+ * @hide
+ */
+ public static final int BAND_5_GHZ_FIRST_CH_NUM = 32;
+ /**
+ * 5 GHz band last channel number
+ * @hide
+ */
+ public static final int BAND_5_GHZ_LAST_CH_NUM = 173;
+ /**
+ * 5 GHz band frequency of first channel in MHz
+ * @hide
+ */
+ public static final int BAND_5_GHZ_START_FREQ_MHZ = 5160;
+ /**
+ * 5 GHz band frequency of last channel in MHz
+ * @hide
+ */
+ public static final int BAND_5_GHZ_END_FREQ_MHZ = 5865;
+
+ /**
+ * 6 GHz band first channel number
+ * @hide
+ */
+ public static final int BAND_6_GHZ_FIRST_CH_NUM = 1;
+ /**
+ * 6 GHz band last channel number
+ * @hide
+ */
+ public static final int BAND_6_GHZ_LAST_CH_NUM = 233;
+ /**
+ * 6 GHz band frequency of first channel in MHz
+ * @hide
+ */
+ public static final int BAND_6_GHZ_START_FREQ_MHZ = 5945;
+ /**
+ * 6 GHz band frequency of last channel in MHz
+ * @hide
+ */
+ public static final int BAND_6_GHZ_END_FREQ_MHZ = 7105;
+
+ /**
+ * Utility function to check if a frequency within 2.4 GHz band
+ * @param freqMhz frequency in MHz
+ * @return true if within 2.4GHz, false otherwise
+ *
+ * @hide
+ */
+ public static boolean is24GHz(int freqMhz) {
+ return freqMhz >= BAND_24_GHZ_START_FREQ_MHZ && freqMhz <= BAND_24_GHZ_END_FREQ_MHZ;
+ }
+
+ /**
+ * Utility function to check if a frequency within 5 GHz band
+ * @param freqMhz frequency in MHz
+ * @return true if within 5GHz, false otherwise
+ *
+ * @hide
+ */
+ public static boolean is5GHz(int freqMhz) {
+ return freqMhz >= BAND_5_GHZ_START_FREQ_MHZ && freqMhz <= BAND_5_GHZ_END_FREQ_MHZ;
+ }
+
+ /**
+ * Utility function to check if a frequency within 6 GHz band
+ * @param freqMhz
+ * @return true if within 6GHz, false otherwise
+ *
+ * @hide
+ */
+ public static boolean is6GHz(int freqMhz) {
+ return freqMhz >= BAND_6_GHZ_START_FREQ_MHZ && freqMhz <= BAND_6_GHZ_END_FREQ_MHZ;
+ }
+
+ /**
+ * Utility function to convert channel number/band to frequency in MHz
+ * @param channel number to convert
+ * @param band of channel to convert
+ * @return center frequency in Mhz of the channel, {@link UNSPECIFIED} if no match
+ *
+ * @hide
+ */
+ public static int convertChannelToFrequencyMhz(int channel, @WifiScanner.WifiBand int band) {
+ if (band == WifiScanner.WIFI_BAND_24_GHZ) {
+ // Special case
+ if (channel == 14) {
+ return 2484;
+ } else if (channel >= BAND_24_GHZ_FIRST_CH_NUM && channel <= BAND_24_GHZ_LAST_CH_NUM) {
+ return ((channel - BAND_24_GHZ_FIRST_CH_NUM) * 5) + BAND_24_GHZ_START_FREQ_MHZ;
+ } else {
+ return UNSPECIFIED;
+ }
+ }
+ if (band == WifiScanner.WIFI_BAND_5_GHZ) {
+ if (channel >= BAND_5_GHZ_FIRST_CH_NUM && channel <= BAND_5_GHZ_LAST_CH_NUM) {
+ return ((channel - BAND_5_GHZ_FIRST_CH_NUM) * 5) + BAND_5_GHZ_START_FREQ_MHZ;
+ } else {
+ return UNSPECIFIED;
+ }
+ }
+ if (band == WifiScanner.WIFI_BAND_6_GHZ) {
+ if (channel >= BAND_6_GHZ_FIRST_CH_NUM && channel <= BAND_6_GHZ_LAST_CH_NUM) {
+ return ((channel - BAND_6_GHZ_FIRST_CH_NUM) * 5) + BAND_6_GHZ_START_FREQ_MHZ;
+ } else {
+ return UNSPECIFIED;
+ }
+ }
+ return UNSPECIFIED;
+ }
+
+ /**
+ * Utility function to convert frequency in MHz to channel number
+ * @param freqMhz frequency in MHz
+ * @return channel number associated with given frequency, {@link UNSPECIFIED} if no match
+ *
+ * @hide
+ */
+ public static int convertFrequencyMhzToChannel(int freqMhz) {
+ // Special case
+ if (freqMhz == 2484) {
+ return 14;
+ } else if (is24GHz(freqMhz)) {
+ return (freqMhz - BAND_24_GHZ_START_FREQ_MHZ) / 5 + BAND_24_GHZ_FIRST_CH_NUM;
+ } else if (is5GHz(freqMhz)) {
+ return ((freqMhz - BAND_5_GHZ_START_FREQ_MHZ) / 5) + BAND_5_GHZ_FIRST_CH_NUM;
+ } else if (is6GHz(freqMhz)) {
+ return ((freqMhz - BAND_6_GHZ_START_FREQ_MHZ) / 5) + BAND_6_GHZ_FIRST_CH_NUM;
+ }
+
+ return UNSPECIFIED;
+ }
+
/**
* @hide
*/
@@ -538,14 +693,6 @@
/**
* @hide
- * TODO: makes real freq boundaries
- */
- public static boolean is24GHz(int freq) {
- return freq > 2400 && freq < 2500;
- }
-
- /**
- * @hide
*/
public boolean is5GHz() {
return ScanResult.is5GHz(frequency);
@@ -560,21 +707,6 @@
/**
* @hide
- * TODO: makes real freq boundaries
- */
- public static boolean is5GHz(int freq) {
- return freq > 4900 && freq < 5900;
- }
-
- /**
- * @hide
- */
- public static boolean is6GHz(int freq) {
- return freq > 5925 && freq < 7125;
- }
-
- /**
- * @hide
*/
public boolean is60GHz() {
return ScanResult.is60GHz(frequency);
diff --git a/wifi/tests/src/android/net/wifi/ScanResultTest.java b/wifi/tests/src/android/net/wifi/ScanResultTest.java
index 6cb8324..5516f43 100644
--- a/wifi/tests/src/android/net/wifi/ScanResultTest.java
+++ b/wifi/tests/src/android/net/wifi/ScanResultTest.java
@@ -46,6 +46,68 @@
ScanResult.WIFI_STANDARD_11AC;
/**
+ * Frequency to channel map. This include some frequencies used outside the US.
+ * Representing it using a vector (instead of map) for simplification.
+ */
+ private static final int[] FREQUENCY_TO_CHANNEL_MAP = {
+ 2412, WifiScanner.WIFI_BAND_24_GHZ, 1,
+ 2417, WifiScanner.WIFI_BAND_24_GHZ, 2,
+ 2422, WifiScanner.WIFI_BAND_24_GHZ, 3,
+ 2427, WifiScanner.WIFI_BAND_24_GHZ, 4,
+ 2432, WifiScanner.WIFI_BAND_24_GHZ, 5,
+ 2437, WifiScanner.WIFI_BAND_24_GHZ, 6,
+ 2442, WifiScanner.WIFI_BAND_24_GHZ, 7,
+ 2447, WifiScanner.WIFI_BAND_24_GHZ, 8,
+ 2452, WifiScanner.WIFI_BAND_24_GHZ, 9,
+ 2457, WifiScanner.WIFI_BAND_24_GHZ, 10,
+ 2462, WifiScanner.WIFI_BAND_24_GHZ, 11,
+ /* 12, 13 are only legitimate outside the US. */
+ 2467, WifiScanner.WIFI_BAND_24_GHZ, 12,
+ 2472, WifiScanner.WIFI_BAND_24_GHZ, 13,
+ /* 14 is for Japan, DSSS and CCK only. */
+ 2484, WifiScanner.WIFI_BAND_24_GHZ, 14,
+ /* 34 valid in Japan. */
+ 5170, WifiScanner.WIFI_BAND_5_GHZ, 34,
+ 5180, WifiScanner.WIFI_BAND_5_GHZ, 36,
+ 5190, WifiScanner.WIFI_BAND_5_GHZ, 38,
+ 5200, WifiScanner.WIFI_BAND_5_GHZ, 40,
+ 5210, WifiScanner.WIFI_BAND_5_GHZ, 42,
+ 5220, WifiScanner.WIFI_BAND_5_GHZ, 44,
+ 5230, WifiScanner.WIFI_BAND_5_GHZ, 46,
+ 5240, WifiScanner.WIFI_BAND_5_GHZ, 48,
+ 5260, WifiScanner.WIFI_BAND_5_GHZ, 52,
+ 5280, WifiScanner.WIFI_BAND_5_GHZ, 56,
+ 5300, WifiScanner.WIFI_BAND_5_GHZ, 60,
+ 5320, WifiScanner.WIFI_BAND_5_GHZ, 64,
+ 5500, WifiScanner.WIFI_BAND_5_GHZ, 100,
+ 5520, WifiScanner.WIFI_BAND_5_GHZ, 104,
+ 5540, WifiScanner.WIFI_BAND_5_GHZ, 108,
+ 5560, WifiScanner.WIFI_BAND_5_GHZ, 112,
+ 5580, WifiScanner.WIFI_BAND_5_GHZ, 116,
+ /* 120, 124, 128 valid in Europe/Japan. */
+ 5600, WifiScanner.WIFI_BAND_5_GHZ, 120,
+ 5620, WifiScanner.WIFI_BAND_5_GHZ, 124,
+ 5640, WifiScanner.WIFI_BAND_5_GHZ, 128,
+ /* 132+ valid in US. */
+ 5660, WifiScanner.WIFI_BAND_5_GHZ, 132,
+ 5680, WifiScanner.WIFI_BAND_5_GHZ, 136,
+ 5700, WifiScanner.WIFI_BAND_5_GHZ, 140,
+ /* 144 is supported by a subset of WiFi chips. */
+ 5720, WifiScanner.WIFI_BAND_5_GHZ, 144,
+ 5745, WifiScanner.WIFI_BAND_5_GHZ, 149,
+ 5765, WifiScanner.WIFI_BAND_5_GHZ, 153,
+ 5785, WifiScanner.WIFI_BAND_5_GHZ, 157,
+ 5805, WifiScanner.WIFI_BAND_5_GHZ, 161,
+ 5825, WifiScanner.WIFI_BAND_5_GHZ, 165,
+ 5845, WifiScanner.WIFI_BAND_5_GHZ, 169,
+ 5865, WifiScanner.WIFI_BAND_5_GHZ, 173,
+ /* Now some 6GHz channels */
+ 5945, WifiScanner.WIFI_BAND_6_GHZ, 1,
+ 5960, WifiScanner.WIFI_BAND_6_GHZ, 4,
+ 6100, WifiScanner.WIFI_BAND_6_GHZ, 32
+ };
+
+ /**
* Setup before tests.
*/
@Before
@@ -184,6 +246,25 @@
}
/**
+ * verify frequency to channel conversion for all possible frequencies.
+ */
+ @Test
+ public void convertFrequencyToChannel() throws Exception {
+ for (int i = 0; i < FREQUENCY_TO_CHANNEL_MAP.length; i += 3) {
+ assertEquals(FREQUENCY_TO_CHANNEL_MAP[i + 2],
+ ScanResult.convertFrequencyMhzToChannel(FREQUENCY_TO_CHANNEL_MAP[i]));
+ }
+ }
+
+ /**
+ * Verify frequency to channel conversion failed for an invalid frequency.
+ */
+ @Test
+ public void convertFrequencyToChannelWithInvalidFreq() throws Exception {
+ assertEquals(-1, ScanResult.convertFrequencyMhzToChannel(8000));
+ }
+
+ /**
* Write the provided {@link ScanResult} to a parcel and deserialize it.
*/
private static ScanResult parcelReadWrite(ScanResult writeResult) throws Exception {