Merge "Move Kotlin test helpers into shared directory" into tm-qpr-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 251cf56..ebafba5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -513,6 +513,9 @@
if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
requiredConstraints |= CONSTRAINT_DEADLINE;
}
+ if (job.isPrefetch()) {
+ requiredConstraints |= CONSTRAINT_PREFETCH;
+ }
boolean exemptedMediaUrisOnly = false;
if (job.getTriggerContentUris() != null) {
requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER;
diff --git a/core/java/android/service/voice/VoiceInteractionManagerInternal.java b/core/java/android/service/voice/VoiceInteractionManagerInternal.java
index b20dccc..a09b74d 100644
--- a/core/java/android/service/voice/VoiceInteractionManagerInternal.java
+++ b/core/java/android/service/voice/VoiceInteractionManagerInternal.java
@@ -17,6 +17,7 @@
package android.service.voice;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.os.Bundle;
import android.os.IBinder;
@@ -65,6 +66,13 @@
public abstract HotwordDetectionServiceIdentity getHotwordDetectionServiceIdentity();
/**
+ * Called by {@code UMS.convertPreCreatedUserIfPossible()} when a new user is not created from
+ * scratched, but converted from the pool of existing pre-created users.
+ */
+ // TODO(b/226201975): remove method once RoleService supports pre-created users
+ public abstract void onPreCreatedUserConversion(@UserIdInt int userId);
+
+ /**
* Provides the uids of the currently active
* {@link android.service.voice.HotwordDetectionService} and its owning package. The
* HotwordDetectionService is an isolated service, so it has a separate uid.
diff --git a/core/java/android/view/IWindowFocusObserver.aidl b/core/java/android/view/IWindowFocusObserver.aidl
index d14bb48..3b23c77 100644
--- a/core/java/android/view/IWindowFocusObserver.aidl
+++ b/core/java/android/view/IWindowFocusObserver.aidl
@@ -16,7 +16,7 @@
package android.view;
/** {@hide} */
-interface IWindowFocusObserver
+oneway interface IWindowFocusObserver
{
void focusGained(IBinder inputToken);
void focusLost(IBinder inputToken);
diff --git a/core/java/android/window/ITaskFragmentOrganizer.aidl b/core/java/android/window/ITaskFragmentOrganizer.aidl
index 3335c9c..79ddadb 100644
--- a/core/java/android/window/ITaskFragmentOrganizer.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizer.aidl
@@ -16,54 +16,9 @@
package android.window;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentTransaction;
/** @hide */
oneway interface ITaskFragmentOrganizer {
- void onTaskFragmentAppeared(in TaskFragmentInfo taskFragmentInfo);
- void onTaskFragmentInfoChanged(in TaskFragmentInfo taskFragmentInfo);
- void onTaskFragmentVanished(in TaskFragmentInfo taskFragmentInfo);
-
- /**
- * Called when the parent leaf Task of organized TaskFragments is changed.
- * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
- * transaction.
- *
- * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
- * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
- * bounds.
- */
- void onTaskFragmentParentInfoChanged(in IBinder fragmentToken, in Configuration parentConfig);
-
- /**
- * Called when the {@link WindowContainerTransaction} created with
- * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
- *
- * @param errorCallbackToken Token set through {@link
- * WindowContainerTransaction#setErrorCallbackToken(IBinder)}
- * @param errorBundle Bundle containing the exception, operation type and TaskFragmentInfo
- * if any. Should be created with
- * {@link TaskFragmentOrganizer#putErrorInfoInBundle}.
- */
- void onTaskFragmentError(in IBinder errorCallbackToken, in Bundle errorBundle);
-
- /**
- * Called when an Activity is reparented to the Task with organized TaskFragment. For example,
- * when an Activity enters and then exits Picture-in-picture, it will be reparented back to its
- * orginial Task. In this case, we need to notify the organizer so that it can check if the
- * Activity matches any split rule.
- *
- * @param taskId The Task that the activity is reparented to.
- * @param activityIntent The intent that the activity is original launched with.
- * @param activityToken If the activity belongs to the same process as the organizer, this
- * will be the actual activity token; if the activity belongs to a
- * different process, the server will generate a temporary token that
- * the organizer can use to reparent the activity through
- * {@link WindowContainerTransaction} if needed.
- */
- void onActivityReparentToTask(int taskId, in Intent activityIntent, in IBinder activityToken);
+ void onTransactionReady(in TaskFragmentTransaction transaction);
}
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index e4a6ad8..c9a5688 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -16,6 +16,13 @@
package android.window;
+import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_TO_TASK;
+import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
+import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
+import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
+import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
+import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
+
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -27,6 +34,7 @@
import android.os.RemoteException;
import android.view.RemoteAnimationDefinition;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -204,6 +212,67 @@
public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
@NonNull IBinder activityToken) {}
+ /**
+ * Called when the transaction is ready so that the organizer can update the TaskFragments based
+ * on the changes in transaction.
+ * @hide
+ */
+ public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
+ final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
+ for (TaskFragmentTransaction.Change change : changes) {
+ // TODO(b/240519866): apply all changes in one WCT.
+ switch (change.getType()) {
+ case TYPE_TASK_FRAGMENT_APPEARED:
+ onTaskFragmentAppeared(change.getTaskFragmentInfo());
+ if (change.getTaskConfiguration() != null) {
+ // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the
+ // same Task
+ onTaskFragmentParentInfoChanged(
+ change.getTaskFragmentToken(),
+ change.getTaskConfiguration());
+ }
+ break;
+ case TYPE_TASK_FRAGMENT_INFO_CHANGED:
+ if (change.getTaskConfiguration() != null) {
+ // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the
+ // same Task
+ onTaskFragmentParentInfoChanged(
+ change.getTaskFragmentToken(),
+ change.getTaskConfiguration());
+ }
+ onTaskFragmentInfoChanged(change.getTaskFragmentInfo());
+ break;
+ case TYPE_TASK_FRAGMENT_VANISHED:
+ onTaskFragmentVanished(change.getTaskFragmentInfo());
+ break;
+ case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
+ onTaskFragmentParentInfoChanged(
+ change.getTaskFragmentToken(),
+ change.getTaskConfiguration());
+ break;
+ case TYPE_TASK_FRAGMENT_ERROR:
+ final Bundle errorBundle = change.getErrorBundle();
+ onTaskFragmentError(
+ change.getErrorCallbackToken(),
+ errorBundle.getParcelable(
+ KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class),
+ errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE),
+ errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION,
+ java.lang.Throwable.class));
+ break;
+ case TYPE_ACTIVITY_REPARENT_TO_TASK:
+ onActivityReparentToTask(
+ change.getTaskId(),
+ change.getActivityIntent(),
+ change.getActivityToken());
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown TaskFragmentEvent=" + change.getType());
+ }
+ }
+ }
+
@Override
public void applyTransaction(@NonNull WindowContainerTransaction t) {
t.setTaskFragmentOrganizer(mInterface);
@@ -221,51 +290,8 @@
private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() {
@Override
- public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
- mExecutor.execute(
- () -> TaskFragmentOrganizer.this.onTaskFragmentAppeared(taskFragmentInfo));
- }
-
- @Override
- public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
- mExecutor.execute(
- () -> TaskFragmentOrganizer.this.onTaskFragmentInfoChanged(taskFragmentInfo));
- }
-
- @Override
- public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
- mExecutor.execute(
- () -> TaskFragmentOrganizer.this.onTaskFragmentVanished(taskFragmentInfo));
- }
-
- @Override
- public void onTaskFragmentParentInfoChanged(
- @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {
- mExecutor.execute(
- () -> TaskFragmentOrganizer.this.onTaskFragmentParentInfoChanged(
- fragmentToken, parentConfig));
- }
-
- @Override
- public void onTaskFragmentError(
- @NonNull IBinder errorCallbackToken, @NonNull Bundle errorBundle) {
- mExecutor.execute(() -> {
- final TaskFragmentInfo info = errorBundle.getParcelable(
- KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class);
- TaskFragmentOrganizer.this.onTaskFragmentError(
- errorCallbackToken, info,
- errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE),
- (Throwable) errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION,
- java.lang.Throwable.class));
- });
- }
-
- @Override
- public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
- @NonNull IBinder activityToken) {
- mExecutor.execute(
- () -> TaskFragmentOrganizer.this.onActivityReparentToTask(
- taskId, activityIntent, activityToken));
+ public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
+ mExecutor.execute(() -> TaskFragmentOrganizer.this.onTransactionReady(transaction));
}
};
diff --git a/core/java/android/window/TaskFragmentTransaction.aidl b/core/java/android/window/TaskFragmentTransaction.aidl
new file mode 100644
index 0000000..aaa2db4
--- /dev/null
+++ b/core/java/android/window/TaskFragmentTransaction.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+parcelable TaskFragmentTransaction;
+parcelable TaskFragmentTransaction.Change;
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
new file mode 100644
index 0000000..755864f
--- /dev/null
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used to communicate information about what are changing on embedded TaskFragments belonging to
+ * the same TaskFragmentOrganizer. A transaction can contain multiple changes.
+ * @see TaskFragmentTransaction.Change
+ * @hide
+ */
+public final class TaskFragmentTransaction implements Parcelable {
+
+ private final ArrayList<Change> mChanges = new ArrayList<>();
+
+ public TaskFragmentTransaction() {}
+
+ private TaskFragmentTransaction(Parcel in) {
+ in.readTypedList(mChanges, Change.CREATOR);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedList(mChanges);
+ }
+
+ /** Adds a {@link Change} to this transaction. */
+ public void addChange(@Nullable Change change) {
+ if (change != null) {
+ mChanges.add(change);
+ }
+ }
+
+ /** Whether this transaction contains any {@link Change}. */
+ public boolean isEmpty() {
+ return mChanges.isEmpty();
+ }
+
+ @NonNull
+ public List<Change> getChanges() {
+ return mChanges;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("TaskFragmentTransaction{changes=[");
+ for (int i = 0; i < mChanges.size(); ++i) {
+ if (i > 0) {
+ sb.append(',');
+ }
+ sb.append(mChanges.get(i));
+ }
+ sb.append("]}");
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<TaskFragmentTransaction> CREATOR = new Creator<>() {
+ @Override
+ public TaskFragmentTransaction createFromParcel(Parcel in) {
+ return new TaskFragmentTransaction(in);
+ }
+
+ @Override
+ public TaskFragmentTransaction[] newArray(int size) {
+ return new TaskFragmentTransaction[size];
+ }
+ };
+
+ /** Change type: the TaskFragment is attached to the hierarchy. */
+ public static final int TYPE_TASK_FRAGMENT_APPEARED = 1;
+
+ /** Change type: the status of the TaskFragment is changed. */
+ public static final int TYPE_TASK_FRAGMENT_INFO_CHANGED = 2;
+
+ /** Change type: the TaskFragment is removed form the hierarchy. */
+ public static final int TYPE_TASK_FRAGMENT_VANISHED = 3;
+
+ /** Change type: the status of the parent leaf Task is changed. */
+ public static final int TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED = 4;
+
+ /** Change type: the TaskFragment related operation failed on the server side. */
+ public static final int TYPE_TASK_FRAGMENT_ERROR = 5;
+
+ /**
+ * Change type: an Activity is reparented to the Task. For example, when an Activity enters and
+ * then exits Picture-in-picture, it will be reparented back to its original Task. In this case,
+ * we need to notify the organizer so that it can check if the Activity matches any split rule.
+ */
+ public static final int TYPE_ACTIVITY_REPARENT_TO_TASK = 6;
+
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_TASK_FRAGMENT_APPEARED,
+ TYPE_TASK_FRAGMENT_INFO_CHANGED,
+ TYPE_TASK_FRAGMENT_VANISHED,
+ TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED,
+ TYPE_TASK_FRAGMENT_ERROR,
+ TYPE_ACTIVITY_REPARENT_TO_TASK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ChangeType {}
+
+ /** Represents the change an embedded TaskFragment undergoes. */
+ public static final class Change implements Parcelable {
+
+ /** @see ChangeType */
+ @ChangeType
+ private final int mType;
+
+ /** @see #setTaskFragmentToken(IBinder) */
+ @Nullable
+ private IBinder mTaskFragmentToken;
+
+ /** @see #setTaskFragmentInfo(TaskFragmentInfo) */
+ @Nullable
+ private TaskFragmentInfo mTaskFragmentInfo;
+
+ /** @see #setTaskId(int) */
+ private int mTaskId;
+
+ /** @see #setTaskConfiguration(Configuration) */
+ @Nullable
+ private Configuration mTaskConfiguration;
+
+ /** @see #setErrorCallbackToken(IBinder) */
+ @Nullable
+ private IBinder mErrorCallbackToken;
+
+ /** @see #setErrorBundle(Bundle) */
+ @Nullable
+ private Bundle mErrorBundle;
+
+ /** @see #setActivityIntent(Intent) */
+ @Nullable
+ private Intent mActivityIntent;
+
+ /** @see #setActivityToken(IBinder) */
+ @Nullable
+ private IBinder mActivityToken;
+
+ public Change(@ChangeType int type) {
+ mType = type;
+ }
+
+ private Change(Parcel in) {
+ mType = in.readInt();
+ mTaskFragmentToken = in.readStrongBinder();
+ mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR);
+ mTaskId = in.readInt();
+ mTaskConfiguration = in.readTypedObject(Configuration.CREATOR);
+ mErrorCallbackToken = in.readStrongBinder();
+ mErrorBundle = in.readBundle(TaskFragmentTransaction.class.getClassLoader());
+ mActivityIntent = in.readTypedObject(Intent.CREATOR);
+ mActivityToken = in.readStrongBinder();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeStrongBinder(mTaskFragmentToken);
+ dest.writeTypedObject(mTaskFragmentInfo, flags);
+ dest.writeInt(mTaskId);
+ dest.writeTypedObject(mTaskConfiguration, flags);
+ dest.writeStrongBinder(mErrorCallbackToken);
+ dest.writeBundle(mErrorBundle);
+ dest.writeTypedObject(mActivityIntent, flags);
+ dest.writeStrongBinder(mActivityToken);
+ }
+
+ /** The change is related to the TaskFragment created with this unique token. */
+ public Change setTaskFragmentToken(@NonNull IBinder taskFragmentToken) {
+ mTaskFragmentToken = requireNonNull(taskFragmentToken);
+ return this;
+ }
+
+ /** Info of the embedded TaskFragment. */
+ public Change setTaskFragmentInfo(@NonNull TaskFragmentInfo info) {
+ mTaskFragmentInfo = requireNonNull(info);
+ return this;
+ }
+
+ /** Task id the parent Task. */
+ public Change setTaskId(int taskId) {
+ mTaskId = taskId;
+ return this;
+ }
+
+ /** Configuration of the parent Task. */
+ public Change setTaskConfiguration(@NonNull Configuration configuration) {
+ mTaskConfiguration = requireNonNull(configuration);
+ return this;
+ }
+
+ /**
+ * If the {@link #TYPE_TASK_FRAGMENT_ERROR} is from a {@link WindowContainerTransaction}
+ * from the {@link TaskFragmentOrganizer}, it may come with an error callback token to
+ * report back.
+ */
+ public Change setErrorCallbackToken(@Nullable IBinder errorCallbackToken) {
+ mErrorCallbackToken = errorCallbackToken;
+ return this;
+ }
+
+ /**
+ * Bundle with necessary info about the failure operation of
+ * {@link #TYPE_TASK_FRAGMENT_ERROR}.
+ */
+ public Change setErrorBundle(@NonNull Bundle errorBundle) {
+ mErrorBundle = requireNonNull(errorBundle);
+ return this;
+ }
+
+ /**
+ * Intent of the activity that is reparented to the Task for
+ * {@link #TYPE_ACTIVITY_REPARENT_TO_TASK}.
+ */
+ public Change setActivityIntent(@NonNull Intent intent) {
+ mActivityIntent = requireNonNull(intent);
+ return this;
+ }
+
+ /**
+ * Token of the reparent activity for {@link #TYPE_ACTIVITY_REPARENT_TO_TASK}.
+ * If the activity belongs to the same process as the organizer, this will be the actual
+ * activity token; if the activity belongs to a different process, the server will generate
+ * a temporary token that the organizer can use to reparent the activity through
+ * {@link WindowContainerTransaction} if needed.
+ */
+ public Change setActivityToken(@NonNull IBinder activityToken) {
+ mActivityToken = requireNonNull(activityToken);
+ return this;
+ }
+
+ @ChangeType
+ public int getType() {
+ return mType;
+ }
+
+ @Nullable
+ public IBinder getTaskFragmentToken() {
+ return mTaskFragmentToken;
+ }
+
+ @Nullable
+ public TaskFragmentInfo getTaskFragmentInfo() {
+ return mTaskFragmentInfo;
+ }
+
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ @Nullable
+ public Configuration getTaskConfiguration() {
+ return mTaskConfiguration;
+ }
+
+ @Nullable
+ public IBinder getErrorCallbackToken() {
+ return mErrorCallbackToken;
+ }
+
+ @Nullable
+ public Bundle getErrorBundle() {
+ return mErrorBundle;
+ }
+
+ @Nullable
+ public Intent getActivityIntent() {
+ return mActivityIntent;
+ }
+
+ @Nullable
+ public IBinder getActivityToken() {
+ return mActivityToken;
+ }
+
+ @Override
+ public String toString() {
+ return "Change{ type=" + mType + " }";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<Change> CREATOR = new Creator<>() {
+ @Override
+ public Change createFromParcel(Parcel in) {
+ return new Change(in);
+ }
+
+ @Override
+ public Change[] newArray(int size) {
+ return new Change[size];
+ }
+ };
+ }
+}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index f691300..fbabf52 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -254,7 +254,7 @@
SystemUiDeviceConfigFlags.IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP,
DEFAULT_IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP);
- private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 125;
+ private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 0;
private static final int URI_PERMISSION_INTENT_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index b32afb4..66fff5c 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -951,7 +951,7 @@
protected void onPostExecute(Drawable d) {
if (getOtherProfile() == mDisplayResolveInfo) {
mResolverListCommunicator.updateProfileViewButton();
- } else {
+ } else if (!mDisplayResolveInfo.hasDisplayIcon()) {
mDisplayResolveInfo.setDisplayIcon(d);
mHolder.bindIcon(mDisplayResolveInfo);
// Notify in case view is already bound to resolve the race conditions on
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 8f10a5e..e625b31 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -55,7 +55,6 @@
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
@@ -155,7 +154,6 @@
// Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
- public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK = 1;
public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2;
public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3;
public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4;
@@ -226,7 +224,7 @@
public static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = {
// This should be mapping to CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE.
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE,
- UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK,
+ NO_STATSD_LOGGING, // This is deprecated.
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE,
@@ -310,7 +308,6 @@
/** @hide */
@IntDef({
CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK,
CUJ_NOTIFICATION_SHADE_SCROLL_FLING,
CUJ_NOTIFICATION_SHADE_ROW_EXPAND,
CUJ_NOTIFICATION_SHADE_ROW_SWIPE,
@@ -738,8 +735,6 @@
switch (cujType) {
case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE:
return "SHADE_EXPAND_COLLAPSE";
- case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK:
- return "SHADE_EXPAND_COLLAPSE_LOCK";
case CUJ_NOTIFICATION_SHADE_SCROLL_FLING:
return "SHADE_SCROLL_FLING";
case CUJ_NOTIFICATION_SHADE_ROW_EXPAND:
diff --git a/core/res/res/values-sw600dp/config.xml b/core/res/res/values-sw600dp/config.xml
index d686dd2..34b6a54 100644
--- a/core/res/res/values-sw600dp/config.xml
+++ b/core/res/res/values-sw600dp/config.xml
@@ -51,7 +51,5 @@
<!-- If true, show multiuser switcher by default unless the user specifically disables it. -->
<bool name="config_showUserSwitcherByDefault">true</bool>
-
- <integer name="config_chooser_max_targets_per_row">6</integer>
</resources>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index c6d9eba..f9bfd58 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1069,6 +1069,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1075136930": {
+ "message": "startLockTaskMode: Can't lock due to auth",
+ "level": "WARN",
+ "group": "WM_DEBUG_LOCKTASK",
+ "at": "com\/android\/server\/wm\/LockTaskController.java"
+ },
"-1069336896": {
"message": "onRootTaskOrderChanged(): rootTask=%s",
"level": "DEBUG",
diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index 6939a72..47de37d 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -359,10 +359,10 @@
/** @hide */
@TestApi
public Region getSafeZone() {
- mMaskMatrix.reset();
+ Path mask = getIconMask();
mMaskMatrix.setScale(SAFEZONE_SCALE, SAFEZONE_SCALE, getBounds().centerX(), getBounds().centerY());
Path p = new Path();
- mMask.transform(mMaskMatrix, p);
+ mask.transform(mMaskMatrix, p);
Region safezoneRegion = new Region(getBounds());
safezoneRegion.setPath(p, safezoneRegion);
return safezoneRegion;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 420d606..586e3a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -495,14 +495,15 @@
mPipBoundsState.getBounds(),
mPipBoundsState.getAspectRatio());
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
- mPipTaskOrganizer.scheduleAnimateResizePip(destinationBounds,
- mEnterAnimationDuration,
- null /* updateBoundsCallback */);
-
- mTouchHandler.onAspectRatioChanged();
- updateMovementBounds(null /* toBounds */, false /* fromRotation */,
- false /* fromImeAdjustment */, false /* fromShelfAdjustment */,
- null /* windowContainerTransaction */);
+ if (!destinationBounds.equals(mPipBoundsState.getBounds())) {
+ mPipTaskOrganizer.scheduleAnimateResizePip(destinationBounds,
+ mEnterAnimationDuration,
+ null /* updateBoundsCallback */);
+ mTouchHandler.onAspectRatioChanged();
+ updateMovementBounds(null /* toBounds */, false /* fromRotation */,
+ false /* fromImeAdjustment */, false /* fromShelfAdjustment */,
+ null /* windowContainerTransaction */);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 1be17f9..de7e7bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -21,7 +21,6 @@
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -86,7 +85,6 @@
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
@@ -411,62 +409,13 @@
}
if (!ENABLE_SHELL_TRANSITIONS) {
- startIntentLegacy(intent, fillInIntent, position, options);
+ mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options);
return;
}
mStageCoordinator.startIntent(intent, fillInIntent, position, options);
}
- private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options) {
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- mStageCoordinator.prepareEvictChildTasks(position, evictWct);
-
- LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
- @Override
- public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback,
- SurfaceControl.Transaction t) {
- if (apps == null || apps.length == 0) {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePosition(SplitLayout.reversePosition(
- mStageCoordinator.getSideStagePosition()));
- }
-
- // Do nothing when the animation was cancelled.
- t.apply();
- return;
- }
-
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) {
- t.show(apps[i].leash);
- }
- }
- t.apply();
-
- if (finishedCallback != null) {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error finishing legacy transition: ", e);
- }
- }
-
- mSyncQueue.queue(evictWct);
- }
- };
-
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
-
- wct.sendPendingIntent(intent, fillInIntent, options);
- mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
- }
-
/** Returns {@code true} if it's launching the same component on both sides of the split. */
@VisibleForTesting
boolean isLaunchingAdjacently(@Nullable Intent startIntent,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 2b3b61b..f2340d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -26,6 +26,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -118,6 +119,7 @@
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
+import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.SplitBounds;
@@ -195,7 +197,6 @@
private boolean mExitSplitScreenOnHide;
private boolean mIsDividerRemoteAnimating;
private boolean mIsExiting;
- private boolean mResizingSplits;
/** The target stage to dismiss to when unlock after folded. */
@StageType
@@ -210,7 +211,11 @@
@Override
public void onLeashReady(SurfaceControl leash) {
- mSyncQueue.runInSync(t -> applyDividerVisibility(t));
+ // This is for avoiding divider invisible due to delay of creating so only need
+ // to do when divider should visible case.
+ if (mDividerVisible) {
+ mSyncQueue.runInSync(t -> applyDividerVisibility(t));
+ }
}
};
@@ -433,6 +438,63 @@
});
}
+ /** Launches an activity into split by legacy transition. */
+ void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
+ @SplitPosition int position, @androidx.annotation.Nullable Bundle options) {
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictChildTasks(position, evictWct);
+
+ LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback,
+ SurfaceControl.Transaction t) {
+ if (apps == null || apps.length == 0) {
+ // Switch the split position if launching as MULTIPLE_TASK failed.
+ if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
+ setSideStagePosition(SplitLayout.reversePosition(
+ getSideStagePosition()), null);
+ }
+
+ // Do nothing when the animation was cancelled.
+ t.apply();
+ return;
+ }
+
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
+ t.apply();
+
+ if (finishedCallback != null) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error finishing legacy transition: ", e);
+ }
+ }
+
+ mSyncQueue.queue(evictWct);
+ }
+ };
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
+
+ // If split still not active, apply windows bounds first to avoid surface reset to
+ // wrong pos by SurfaceAnimator from wms.
+ // TODO(b/223325631): check is it still necessary after improve enter transition done.
+ if (!mMainStage.isActive()) {
+ updateWindowBounds(mSplitLayout, wct);
+ }
+
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+ }
+
/** Starts 2 tasks in one transition. */
void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
@Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio,
@@ -849,6 +911,7 @@
.setWindowCrop(mSideStage.mRootLeash, null);
t.setPosition(mMainStage.mRootLeash, 0, 0)
.setPosition(mSideStage.mRootLeash, 0, 0);
+ t.hide(mMainStage.mDimLayer).hide(mSideStage.mDimLayer);
setDividerVisibility(false, t);
// In this case, exit still under progress, fade out the split decor after first WCT
@@ -858,7 +921,7 @@
WindowContainerTransaction finishedWCT = new WindowContainerTransaction();
mIsExiting = false;
childrenToTop.dismiss(finishedWCT, true /* toTop */);
- wct.reorder(mRootTaskInfo.token, false /* toTop */);
+ finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
mTaskOrganizer.applyTransaction(finishedWCT);
onTransitionAnimationComplete();
});
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml b/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml
index a596a9a..7c3f5a5 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml
@@ -28,8 +28,7 @@
style="@style/SettingsLibActionButton"
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_weight="1"
- android:hyphenationFrequency="normalFast"/>
+ android:layout_weight="1"/>
<View
android:id="@+id/divider1"
@@ -44,8 +43,7 @@
style="@style/SettingsLibActionButton"
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_weight="1"
- android:hyphenationFrequency="normalFast"/>
+ android:layout_weight="1"/>
<View
android:id="@+id/divider2"
@@ -60,8 +58,7 @@
style="@style/SettingsLibActionButton"
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_weight="1"
- android:hyphenationFrequency="normalFast"/>
+ android:layout_weight="1"/>
<View
android:id="@+id/divider3"
@@ -76,6 +73,5 @@
style="@style/SettingsLibActionButton"
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_weight="1"
- android:hyphenationFrequency="normalFast"/>
+ android:layout_weight="1"/>
</LinearLayout>
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/layout-v33/settingslib_action_buttons.xml b/packages/SettingsLib/ActionButtonsPreference/res/layout-v33/settingslib_action_buttons.xml
new file mode 100644
index 0000000..b4640ee
--- /dev/null
+++ b/packages/SettingsLib/ActionButtonsPreference/res/layout-v33/settingslib_action_buttons.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:paddingHorizontal="8dp"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/button1"
+ style="@style/SettingsLibActionButton"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:lineBreakWordStyle="phrase"
+ android:hyphenationFrequency="normalFast"/>
+
+ <View
+ android:id="@+id/divider1"
+ android:layout_width="1dp"
+ android:layout_height="match_parent"
+ android:paddingHorizontal="4dp"
+ android:visibility="gone"
+ android:background="?android:colorBackground" />
+
+ <Button
+ android:id="@+id/button2"
+ style="@style/SettingsLibActionButton"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:lineBreakWordStyle="phrase"
+ android:hyphenationFrequency="normalFast"/>
+
+ <View
+ android:id="@+id/divider2"
+ android:layout_width="1dp"
+ android:layout_height="match_parent"
+ android:paddingHorizontal="4dp"
+ android:visibility="gone"
+ android:background="?android:colorBackground" />
+
+ <Button
+ android:id="@+id/button3"
+ style="@style/SettingsLibActionButton"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:lineBreakWordStyle="phrase"
+ android:hyphenationFrequency="normalFast"/>
+
+ <View
+ android:id="@+id/divider3"
+ android:layout_width="1dp"
+ android:layout_height="match_parent"
+ android:paddingHorizontal="4dp"
+ android:visibility="gone"
+ android:background="?android:colorBackground" />
+
+ <Button
+ android:id="@+id/button4"
+ style="@style/SettingsLibActionButton"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:lineBreakWordStyle="phrase"
+ android:hyphenationFrequency="normalFast"/>
+</LinearLayout>
diff --git a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
new file mode 100644
index 0000000..8975857
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="start|center_vertical"
+ android:minWidth="@dimen/secondary_app_icon_size"
+ android:orientation="horizontal"
+ android:paddingEnd="16dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="@dimen/secondary_app_icon_size"
+ android:layout_height="@dimen/secondary_app_icon_size"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textDirection="locale"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"/>
+
+ <TextView
+ android:id="@+id/appendix"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ android:visibility="gone"/>
+
+ <ProgressBar
+ android:id="@android:id/progress"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4dp"
+ android:max="100"
+ android:visibility="gone"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical|end"
+ android:minWidth="@dimen/two_target_min_width"
+ android:orientation="vertical"/>
+
+</LinearLayout>
diff --git a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app_header.xml b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app_header.xml
new file mode 100644
index 0000000..3a219b9
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app_header.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingBottom="16dp"
+ android:paddingTop="8dp"
+ android:clickable="false">
+
+ <TextView
+ android:id="@+id/apps_top_intro_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textDirection="locale"
+ android:clickable="false"
+ android:longClickable="false"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="@style/TextAppearance.TopIntroText" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
index 12db901..e65f7de 100644
--- a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
@@ -55,7 +55,6 @@
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"/>
<TextView
@@ -63,7 +62,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textDirection="locale"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"/>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index ec091bf..f4af9c7 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -19,6 +19,7 @@
import static android.text.Layout.HYPHENATION_FREQUENCY_NORMAL_FAST;
import android.app.ActionBar;
+import android.graphics.text.LineBreakConfig;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
@@ -88,6 +89,12 @@
mCollapsingToolbarLayout.setLineSpacingMultiplier(TOOLBAR_LINE_SPACING_MULTIPLIER);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
mCollapsingToolbarLayout.setHyphenationFrequency(HYPHENATION_FREQUENCY_NORMAL_FAST);
+ mCollapsingToolbarLayout.setStaticLayoutBuilderConfigurer(builder ->
+ builder.setLineBreakConfig(
+ new LineBreakConfig.Builder()
+ .setLineBreakWordStyle(
+ LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+ .build()));
}
}
disableCollapsingToolbarLayoutScrollingBehavior();
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
index a8c7a3f..522de93 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -22,6 +22,7 @@
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.text.LineBreakConfig;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -110,6 +111,12 @@
mCollapsingToolbarLayout.setLineSpacingMultiplier(TOOLBAR_LINE_SPACING_MULTIPLIER);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
mCollapsingToolbarLayout.setHyphenationFrequency(HYPHENATION_FREQUENCY_NORMAL_FAST);
+ mCollapsingToolbarLayout.setStaticLayoutBuilderConfigurer(builder ->
+ builder.setLineBreakConfig(
+ new LineBreakConfig.Builder()
+ .setLineBreakWordStyle(
+ LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+ .build()));
}
if (!TextUtils.isEmpty(mToolbarTitle)) {
mCollapsingToolbarLayout.setTitle(mToolbarTitle);
diff --git a/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml
index 2c1fdd4..42700b3 100644
--- a/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml
+++ b/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml
@@ -53,7 +53,6 @@
android:paddingTop="16dp"
android:paddingBottom="8dp"
android:textColor="?android:attr/textColorSecondary"
- android:hyphenationFrequency="normalFast"
android:ellipsize="marquee" />
<com.android.settingslib.widget.LinkTextView
diff --git a/packages/SettingsLib/FooterPreference/res/layout-v33/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout-v33/preference_footer.xml
new file mode 100644
index 0000000..a2f2510
--- /dev/null
+++ b/packages/SettingsLib/FooterPreference/res/layout-v33/preference_footer.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:orientation="vertical"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="56dp"
+ android:gravity="start|top"
+ android:orientation="horizontal"
+ android:paddingEnd="12dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="4dp">
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:ellipsize="marquee" />
+
+ <com.android.settingslib.widget.LinkTextView
+ android:id="@+id/settingslib_learn_more"
+ android:text="@string/settingslib_learn_more_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="8dp"
+ android:clickable="true"
+ android:visibility="gone"
+ style="@style/TextAppearance.Footer.Title.SettingsLib"/>
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
new file mode 100644
index 0000000..35d1323
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:background="?android:attr/colorBackground"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/frame"
+ android:minHeight="@dimen/settingslib_min_switch_bar_height"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_margin="@dimen/settingslib_switchbar_margin"
+ android:paddingStart="@dimen/settingslib_switchbar_padding_left"
+ android:paddingEnd="@dimen/settingslib_switchbar_padding_right">
+
+ <TextView
+ android:id="@+id/switch_text"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_marginEnd="@dimen/settingslib_switch_title_margin"
+ android:layout_marginVertical="@dimen/settingslib_switch_title_margin"
+ android:layout_gravity="center_vertical"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ style="@style/MainSwitchText.Settingslib" />
+
+ <ImageView
+ android:id="@+id/restricted_icon"
+ android:layout_width="@dimen/settingslib_restricted_icon_size"
+ android:layout_height="@dimen/settingslib_restricted_icon_size"
+ android:tint="?android:attr/colorAccent"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="@dimen/settingslib_restricted_icon_margin_end"
+ android:src="@drawable/settingslib_ic_info"
+ android:visibility="gone" />
+
+ <Switch
+ android:id="@android:id/switch_widget"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical"
+ android:focusable="false"
+ android:clickable="false"
+ android:theme="@style/Switch.SettingsLib"/>
+ </LinearLayout>
+
+</LinearLayout>
+
+
diff --git a/packages/SettingsLib/RadioButtonPreference/res/layout-v33/preference_radio.xml b/packages/SettingsLib/RadioButtonPreference/res/layout-v33/preference_radio.xml
new file mode 100644
index 0000000..bb8ac28
--- /dev/null
+++ b/packages/SettingsLib/RadioButtonPreference/res/layout-v33/preference_radio.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingHorizontal="20dp"
+ android:gravity="center"
+ android:minWidth="56dp"
+ android:orientation="vertical"/>
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:minWidth="32dp"
+ android:orientation="horizontal"
+ android:layout_marginEnd="16dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <androidx.preference.internal.PreferenceImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ settings:maxWidth="@dimen/secondary_app_icon_size"
+ settings:maxHeight="@dimen/secondary_app_icon_size"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+ <LinearLayout
+ android:id="@+id/summary_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="viewStart"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textColor="?android:attr/textColorSecondary"/>
+
+ <TextView
+ android:id="@+id/appendix"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="viewEnd"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="1"
+ android:visibility="gone"
+ android:ellipsize="end"/>
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/radio_extra_widget_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+ <View
+ android:layout_width=".75dp"
+ android:layout_height="32dp"
+ android:layout_marginTop="16dp"
+ android:layout_marginBottom="16dp"
+ android:background="?android:attr/dividerVertical" />
+ <ImageView
+ android:id="@+id/radio_extra_widget"
+ android:layout_width="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:layout_height="fill_parent"
+ android:src="@drawable/ic_settings_accent"
+ android:contentDescription="@string/settings_label"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackground" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
index 64d100a..906ff2c 100644
--- a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
+++ b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
@@ -66,7 +66,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"/>
<LinearLayout
@@ -81,7 +80,6 @@
android:layout_weight="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textAlignment="viewStart"
- android:hyphenationFrequency="normalFast"
android:textColor="?android:attr/textColorSecondary"/>
<TextView
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml
new file mode 100644
index 0000000..0b27464
--- /dev/null
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingHorizontal="20dp"
+ android:gravity="center"
+ android:minWidth="56dp"
+ android:orientation="vertical"/>
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:minWidth="32dp"
+ android:orientation="horizontal"
+ android:layout_marginEnd="16dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <androidx.preference.internal.PreferenceImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ settings:maxWidth="@dimen/secondary_app_icon_size"
+ settings:maxHeight="@dimen/secondary_app_icon_size"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+ <LinearLayout
+ android:id="@+id/summary_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="viewStart"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textColor="?android:attr/textColorSecondary"/>
+
+ <TextView
+ android:id="@+id/appendix"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="viewEnd"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="1"
+ android:visibility="gone"
+ android:ellipsize="end"/>
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/selector_extra_widget_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+ <View
+ android:layout_width=".75dp"
+ android:layout_height="32dp"
+ android:layout_marginTop="16dp"
+ android:layout_marginBottom="16dp"
+ android:background="?android:attr/dividerVertical" />
+ <ImageView
+ android:id="@+id/selector_extra_widget"
+ android:layout_width="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:layout_height="fill_parent"
+ android:src="@drawable/ic_settings_accent"
+ android:contentDescription="@string/settings_label"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackground" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml
index 2a550ef..8bb56ff 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml
@@ -66,7 +66,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"/>
<LinearLayout
@@ -81,7 +80,6 @@
android:layout_weight="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textAlignment="viewStart"
- android:hyphenationFrequency="normalFast"
android:textColor="?android:attr/textColorSecondary"/>
<TextView
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml
index d4d466a..23aa993 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml
@@ -43,7 +43,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee"/>
@@ -58,7 +57,6 @@
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="10"
- android:hyphenationFrequency="normalFast"
style="@style/PreferenceSummaryTextStyle"/>
</RelativeLayout>
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml
new file mode 100644
index 0000000..70ce374
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipToPadding="false"
+ android:baselineAligned="false">
+
+ <include layout="@layout/settingslib_icon_frame"/>
+
+ <RelativeLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:ellipsize="marquee"/>
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignLeft="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="10"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ style="@style/PreferenceSummaryTextStyle"/>
+
+ </RelativeLayout>
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="end|center_vertical"
+ android:paddingLeft="16dp"
+ android:paddingStart="16dp"
+ android:paddingRight="0dp"
+ android:paddingEnd="0dp"
+ android:orientation="vertical"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml
new file mode 100644
index 0000000..6046d91
--- /dev/null
+++ b/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingBottom="16dp"
+ android:paddingTop="8dp"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipToPadding="false">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="false"
+ android:longClickable="false"
+ android:maxLines="10"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="@style/TextAppearance.TopIntroText"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
index b2a9037..4d6e1b7 100644
--- a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
+++ b/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
@@ -33,6 +33,5 @@
android:clickable="false"
android:longClickable="false"
android:maxLines="10"
- android:hyphenationFrequency="normalFast"
android:textAppearance="@style/TextAppearance.TopIntroText"/>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target.xml
index ac5807d..2c35772 100644
--- a/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target.xml
+++ b/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target.xml
@@ -41,7 +41,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee"/>
@@ -53,7 +52,6 @@
android:layout_alignStart="@android:id/title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
- android:hyphenationFrequency="normalFast"
android:maxLines="10"/>
</RelativeLayout>
diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v33/preference_two_target.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v33/preference_two_target.xml
new file mode 100644
index 0000000..0bca9ab
--- /dev/null
+++ b/packages/SettingsLib/TwoTargetPreference/res/layout-v33/preference_two_target.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+
+<!-- Based off preference_material_settings.xml except that ripple on only on the left side. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:clipToPadding="false">
+
+ <include layout="@layout/settingslib_icon_frame"/>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:ellipsize="marquee"/>
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:maxLines="10"/>
+
+ </RelativeLayout>
+
+ <include layout="@layout/preference_two_target_divider" />
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:gravity="center"
+ android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/res/layout-v33/preference_access_point.xml b/packages/SettingsLib/res/layout-v33/preference_access_point.xml
new file mode 100644
index 0000000..81bfeffd
--- /dev/null
+++ b/packages/SettingsLib/res/layout-v33/preference_access_point.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+
+<!-- Based off preference_two_target.xml with Material ripple moved to parent for full ripple. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="start|center_vertical"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="start|center_vertical"
+ android:minWidth="48dp"
+ android:orientation="horizontal"
+ android:clipToPadding="false"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <androidx.preference.internal.PreferenceImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ settings:maxWidth="48dp"
+ settings:maxHeight="48dp" />
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:ellipsize="marquee" />
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:maxLines="10" />
+
+ </RelativeLayout>
+
+ </LinearLayout>
+
+ <include layout="@layout/preference_two_target_divider" />
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:gravity="center"
+ android:orientation="vertical" />
+
+ <ImageButton
+ android:id="@+id/icon_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:minHeight="@dimen/min_tap_target_size"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackground"
+ android:visibility="gone">
+ </ImageButton>
+</LinearLayout>
diff --git a/packages/SettingsLib/res/layout-v33/preference_checkable_two_target.xml b/packages/SettingsLib/res/layout-v33/preference_checkable_two_target.xml
new file mode 100644
index 0000000..7ad018c
--- /dev/null
+++ b/packages/SettingsLib/res/layout-v33/preference_checkable_two_target.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+
+<!-- Based off preference_material_settings.xml except that ripple on only on the left side. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:background="@android:color/transparent"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="start|center_vertical"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/checkbox_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="start|center_vertical"
+ android:minWidth="48dp"
+ android:minHeight="48dp"
+ android:orientation="horizontal"
+ android:clipToPadding="false"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <include layout="@layout/preference_widget_checkbox" />
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:ellipsize="marquee" />
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:maxLines="10" />
+
+ </RelativeLayout>
+
+ </LinearLayout>
+
+ <include layout="@layout/preference_two_target_divider" />
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:gravity="center"
+ android:orientation="vertical" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/layout-v33/restricted_switch_preference.xml b/packages/SettingsLib/res/layout-v33/restricted_switch_preference.xml
new file mode 100644
index 0000000..31e9696
--- /dev/null
+++ b/packages/SettingsLib/res/layout-v33/restricted_switch_preference.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="56dp"
+ android:gravity="start|center_vertical"
+ android:orientation="horizontal"
+ android:paddingEnd="12dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <com.android.internal.widget.PreferenceImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxWidth="48dp"
+ android:maxHeight="48dp" />
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:ellipsize="marquee" />
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:maxLines="10" />
+
+ <TextView
+ android:id="@+id/additional_summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/summary"
+ android:layout_alignStart="@android:id/summary"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="10"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:visibility="gone" />
+ </RelativeLayout>
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="end|center_vertical"
+ android:paddingStart="16dp"
+ android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/res/layout/preference_access_point.xml b/packages/SettingsLib/res/layout/preference_access_point.xml
index 4ad9d80..802d604 100644
--- a/packages/SettingsLib/res/layout/preference_access_point.xml
+++ b/packages/SettingsLib/res/layout/preference_access_point.xml
@@ -65,7 +65,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee" />
@@ -77,7 +76,6 @@
android:layout_alignStart="@android:id/title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
- android:hyphenationFrequency="normalFast"
android:maxLines="10" />
</RelativeLayout>
diff --git a/packages/SettingsLib/res/layout/preference_checkable_two_target.xml b/packages/SettingsLib/res/layout/preference_checkable_two_target.xml
index cbe49cd..f512f9b 100644
--- a/packages/SettingsLib/res/layout/preference_checkable_two_target.xml
+++ b/packages/SettingsLib/res/layout/preference_checkable_two_target.xml
@@ -62,7 +62,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee" />
@@ -74,7 +73,6 @@
android:layout_alignStart="@android:id/title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
- android:hyphenationFrequency="normalFast"
android:maxLines="10" />
</RelativeLayout>
diff --git a/packages/SettingsLib/res/layout/restricted_switch_preference.xml b/packages/SettingsLib/res/layout/restricted_switch_preference.xml
index edea144..169ae97 100644
--- a/packages/SettingsLib/res/layout/restricted_switch_preference.xml
+++ b/packages/SettingsLib/res/layout/restricted_switch_preference.xml
@@ -52,7 +52,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee" />
@@ -63,7 +62,6 @@
android:layout_alignStart="@android:id/title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
- android:hyphenationFrequency="normalFast"
android:maxLines="10" />
<TextView android:id="@+id/additional_summary"
@@ -74,7 +72,6 @@
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="10"
- android:hyphenationFrequency="normalFast"
android:visibility="gone" />
</RelativeLayout>
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
index 1a08366..b416738 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
@@ -44,6 +44,8 @@
private final Handler mReceiverHandler;
private final MobileTelephonyCallback mTelephonyCallback;
+ private boolean mListening = false;
+
/**
* MobileStatusTracker constructors
*
@@ -76,6 +78,7 @@
* Config the MobileStatusTracker to start or stop monitoring platform signals.
*/
public void setListening(boolean listening) {
+ mListening = listening;
if (listening) {
mPhone.registerTelephonyCallback(mReceiverHandler::post, mTelephonyCallback);
} else {
@@ -83,6 +86,10 @@
}
}
+ public boolean isListening() {
+ return mListening;
+ }
+
private void updateDataSim() {
int activeDataSubId = mDefaults.getActiveDataSubId();
if (SubscriptionManager.isValidSubscriptionId(activeDataSubId)) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index d427a57..ff64c78 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -485,13 +485,7 @@
val out = mutableListOf<List<PositionedGlyphs>>()
for (lineNo in 0 until layout.lineCount) { // Shape all lines.
val lineStart = layout.getLineStart(lineNo)
- var count = layout.getLineEnd(lineNo) - lineStart
- // Do not render the last character in the line if it's a newline and unprintable
- val last = lineStart + count - 1
- if (last > lineStart && last < layout.text.length && layout.text[last] == '\n') {
- count--
- }
-
+ val count = layout.getLineEnd(lineNo) - lineStart
val runs = mutableListOf<PositionedGlyphs>()
TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic,
paint) { _, _, glyphs, _ ->
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index cafdc86..0c191607 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -13,9 +13,9 @@
*/
package com.android.systemui.plugins
-import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.view.View
+import com.android.internal.colorextraction.ColorExtractor
import com.android.systemui.plugins.annotations.ProvidesInterface
import java.io.PrintWriter
import java.util.Locale
@@ -57,15 +57,7 @@
val events: ClockEvents
/** Triggers for various animations */
- val animations: ClockAnimations
-
- /** Initializes various rendering parameters. If never called, provides reasonable defaults. */
- fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
- events.onColorPaletteChanged(resources)
- animations.doze(dozeFraction)
- animations.fold(foldFraction)
- events.onTimeTick()
- }
+ val animation: ClockAnimation
/** Optional method for dumping debug information */
fun dump(pw: PrintWriter) { }
@@ -88,12 +80,15 @@
/** Call whenever font settings change */
fun onFontSettingChanged() { }
- /** Call whenever the color palette should update */
- fun onColorPaletteChanged(resources: Resources) { }
+ /** Call whenever the color pallete should update */
+ fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) { }
}
/** Methods which trigger various clock animations */
-interface ClockAnimations {
+interface ClockAnimation {
+ /** Initializes the doze & fold animation positions. Defaults to neither folded nor dozing. */
+ fun initialize(dozeFraction: Float, foldFraction: Float) { }
+
/** Runs an enter animation (if any) */
fun enter() { }
diff --git a/packages/SystemUI/res-keyguard/font/clock.xml b/packages/SystemUI/res-keyguard/font/clock.xml
new file mode 100644
index 0000000..0137dc3
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/font/clock.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** 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.
+*/
+-->
+
+<!--
+** AOD/LockScreen Clock font.
+** Should include all numeric glyphs in all supported locales.
+** Recommended: font with variable width to support AOD => LS animations
+-->
+<!-- TODO: Remove when clock migration complete -->
+<font-family xmlns:android="http://schemas.android.com/apk/res/android">
+ <font android:typeface="monospace"/>
+</font-family>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 8b8ebf0..6a38507 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -31,14 +31,42 @@
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:paddingStart="@dimen/clock_padding_start">
+ <com.android.systemui.shared.clocks.AnimatableClockView
+ android:id="@+id/animatable_clock_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:gravity="start"
+ android:textSize="@dimen/clock_text_size"
+ android:fontFamily="@font/clock"
+ android:elegantTextHeight="false"
+ android:singleLine="true"
+ android:fontFeatureSettings="pnum"
+ chargeAnimationDelay="350"
+ dozeWeight="200"
+ lockScreenWeight="400"
+ />
</FrameLayout>
<FrameLayout
android:id="@+id/lockscreen_clock_view_large"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:layout_below="@id/keyguard_slice_view"
- android:paddingTop="@dimen/keyguard_large_clock_top_padding"
android:visibility="gone">
+ <com.android.systemui.shared.clocks.AnimatableClockView
+ android:id="@+id/animatable_clock_view_large"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center_horizontal"
+ android:textSize="@dimen/large_clock_text_size"
+ android:fontFamily="@font/clock"
+ android:typeface="monospace"
+ android:elegantTextHeight="false"
+ chargeAnimationDelay="200"
+ dozeWeight="200"
+ lockScreenWeight="400"
+ />
</FrameLayout>
<!-- Not quite optimal but needed to translate these items as a group. The
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index 5f4e310..7a57293 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -19,7 +19,7 @@
android:id="@+id/time_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:fontFamily="@*android:string/config_clockFontFamily"
+ android:fontFamily="@font/clock"
android:includeFontPadding="false"
android:textColor="@android:color/white"
android:format12Hour="@string/dream_time_complication_12_hr_time_format"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 205b117..3fb00a3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -664,7 +664,13 @@
<!-- When large clock is showing, offset the smartspace by this amount -->
<dimen name="keyguard_smartspace_top_offset">12dp</dimen>
<!-- With the large clock, move up slightly from the center -->
- <dimen name="keyguard_large_clock_top_padding">100dp</dimen>
+ <dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
+
+ <!-- TODO: Remove during migration -->
+ <!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
+ <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
+ <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
+ <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
<dimen name="notification_scrim_corner_radius">32dp</dimen>
@@ -884,6 +890,11 @@
burn-in on AOD. -->
<dimen name="burn_in_prevention_offset_y_clock">42dp</dimen>
+ <!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
+ <!-- TODO: Remove when clock migration complete -->
+ <dimen name="large_clock_text_size">150dp</dimen>
+ <dimen name="clock_text_size">86dp</dimen>
+
<!-- The maximum offset in either direction that icons move to prevent burn-in on AOD. -->
<dimen name="default_burn_in_prevention_offset">15dp</dimen>
diff --git a/packages/SystemUI/shared/res/layout/clock_default_large.xml b/packages/SystemUI/shared/res/layout/clock_default_large.xml
index 0139d50..8510a0a 100644
--- a/packages/SystemUI/shared/res/layout/clock_default_large.xml
+++ b/packages/SystemUI/shared/res/layout/clock_default_large.xml
@@ -18,6 +18,7 @@
-->
<com.android.systemui.shared.clocks.AnimatableClockView
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/animatable_clock_view_large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
diff --git a/packages/SystemUI/shared/res/layout/clock_default_small.xml b/packages/SystemUI/shared/res/layout/clock_default_small.xml
index 390ff5e..ec0e427 100644
--- a/packages/SystemUI/shared/res/layout/clock_default_small.xml
+++ b/packages/SystemUI/shared/res/layout/clock_default_small.xml
@@ -18,6 +18,7 @@
-->
<com.android.systemui.shared.clocks.AnimatableClockView
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/animatable_clock_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
@@ -25,7 +26,6 @@
android:textSize="@dimen/small_clock_text_size"
android:fontFamily="@*android:string/config_clockFontFamily"
android:elegantTextHeight="false"
- android:ellipsize="none"
android:singleLine="true"
android:fontFeatureSettings="pnum"
chargeAnimationDelay="350"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 8f1959e..2739d59 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -20,15 +20,12 @@
import android.annotation.FloatRange
import android.annotation.IntRange
import android.annotation.SuppressLint
-import android.app.compat.ChangeIdStateCache.invalidate
import android.content.Context
import android.graphics.Canvas
import android.text.TextUtils
import android.text.format.DateFormat
import android.util.AttributeSet
import android.widget.TextView
-import com.android.internal.R.attr.contentDescription
-import com.android.internal.R.attr.format
import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.TextAnimator
@@ -78,12 +75,6 @@
val lockScreenWeight: Int
get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
- /**
- * The number of pixels below the baseline. For fonts that support languages such as
- * Burmese, this space can be significant and should be accounted for when computing layout.
- */
- val bottom get() = paint?.fontMetrics?.bottom ?: 0f
-
init {
val animatableClockViewAttributes = context.obtainStyledAttributes(
attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes
@@ -142,15 +133,6 @@
// relayout if the text didn't actually change.
if (!TextUtils.equals(text, formattedText)) {
text = formattedText
-
- // Because the TextLayout may mutate under the hood as a result of the new text, we
- // notify the TextAnimator that it may have changed and request a measure/layout. A
- // crash will occur on the next invocation of setTextStyle if the layout is mutated
- // without being notified TextInterpolator being notified.
- if (layout != null) {
- textAnimator?.updateLayout(layout)
- }
- requestLayout()
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 835d6e9..e707d4d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -35,6 +35,8 @@
private val TAG = ClockRegistry::class.simpleName
private val DEBUG = true
+typealias ClockChangeListener = () -> Unit
+
/** ClockRegistry aggregates providers and plugins */
open class ClockRegistry(
val context: Context,
@@ -49,11 +51,6 @@
defaultClockProvider: DefaultClockProvider
) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { }
- // Usually this would be a typealias, but a SAM provides better java interop
- fun interface ClockChangeListener {
- fun onClockChanged()
- }
-
var isEnabled: Boolean = false
private val gson = Gson()
@@ -61,7 +58,7 @@
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
private val settingObserver = object : ContentObserver(handler) {
override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
- clockChangeListeners.forEach { it.onClockChanged() }
+ clockChangeListeners.forEach { it() }
}
private val pluginListener = object : PluginListener<ClockProviderPlugin> {
@@ -120,11 +117,8 @@
val id = clock.clockId
val current = availableClocks[id]
if (current != null) {
- Log.e(
- TAG,
- "Clock Id conflict: $id is registered by both " +
- "${provider::class.simpleName} and ${current.provider::class.simpleName}"
- )
+ Log.e(TAG, "Clock Id conflict: $id is registered by both " +
+ "${provider::class.simpleName} and ${current.provider::class.simpleName}")
return
}
@@ -133,7 +127,7 @@
if (DEBUG) {
Log.i(TAG, "Current clock ($currentId) was connected")
}
- clockChangeListeners.forEach { it.onClockChanged() }
+ clockChangeListeners.forEach { it() }
}
}
}
@@ -145,7 +139,7 @@
if (currentId == clock.clockId) {
Log.w(TAG, "Current clock ($currentId) was disconnected")
- clockChangeListeners.forEach { it.onClockChanged() }
+ clockChangeListeners.forEach { it() }
}
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 1d8abe3..5d8da59 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -19,9 +19,10 @@
import android.icu.text.NumberFormat
import android.util.TypedValue
import android.view.LayoutInflater
+import com.android.internal.colorextraction.ColorExtractor
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.Clock
-import com.android.systemui.plugins.ClockAnimations
+import com.android.systemui.plugins.ClockAnimation
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
@@ -101,13 +102,10 @@
TypedValue.COMPLEX_UNIT_PX,
resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
)
- recomputePadding()
}
- override fun onColorPaletteChanged(resources: Resources) {
- val color = resources.getColor(android.R.color.system_accent1_100)
- clocks.forEach { it.setColors(DOZE_COLOR, color) }
- }
+ override fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) =
+ clocks.forEach { it.setColors(DOZE_COLOR, palette.mainColor) }
override fun onLocaleChanged(locale: Locale) {
val nf = NumberFormat.getInstance(locale)
@@ -121,17 +119,8 @@
}
}
- override var animations = DefaultClockAnimations(0f, 0f)
- private set
-
- inner class DefaultClockAnimations(
- dozeFraction: Float,
- foldFraction: Float
- ) : ClockAnimations {
- private var foldState = AnimationState(0f)
- private var dozeState = AnimationState(0f)
-
- init {
+ override val animation = object : ClockAnimation {
+ override fun initialize(dozeFraction: Float, foldFraction: Float) {
dozeState = AnimationState(dozeFraction)
foldState = AnimationState(foldFraction)
@@ -143,13 +132,14 @@
}
override fun enter() {
- if (!dozeState.isActive) {
+ if (dozeState.isActive) {
clocks.forEach { it.animateAppearOnLockscreen() }
}
}
override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } }
+ private var foldState = AnimationState(0f)
override fun fold(fraction: Float) {
val (hasChanged, hasJumped) = foldState.update(fraction)
if (hasChanged) {
@@ -157,6 +147,7 @@
}
}
+ private var dozeState = AnimationState(0f)
override fun doze(fraction: Float) {
val (hasChanged, hasJumped) = dozeState.update(fraction)
if (hasChanged) {
@@ -181,19 +172,6 @@
init {
events.onLocaleChanged(Locale.getDefault())
- clocks.forEach { it.setColors(DOZE_COLOR, DOZE_COLOR) }
- }
-
- override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
- recomputePadding()
- animations = DefaultClockAnimations(dozeFraction, foldFraction)
- events.onColorPaletteChanged(resources)
- events.onTimeTick()
- }
-
- private fun recomputePadding() {
- val topPadding = -1 * (largeClock.bottom.toInt() - 180)
- largeClock.setPadding(0, topPadding, 0, 0)
}
override fun dump(pw: PrintWriter) = clocks.forEach { it.dump(pw) }
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index e0b11d8..c69ff7e 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -182,6 +182,18 @@
mStatusBarStateController.removeCallback(mStatusBarStateListener);
}
+ /**
+ * @return the number of pixels below the baseline. For fonts that support languages such as
+ * Burmese, this space can be significant.
+ */
+ public float getBottom() {
+ if (mView.getPaint() != null && mView.getPaint().getFontMetrics() != null) {
+ return mView.getPaint().getFontMetrics().bottom;
+ }
+
+ return 0f;
+ }
+
/** Animate the clock appearance */
public void animateAppear() {
if (!mIsDozing) mView.animateAppearOnLockscreen();
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
deleted file mode 100644
index efd7bcf..0000000
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2022 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.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.content.res.Resources
-import android.text.format.DateFormat
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.plugins.Clock
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
-import com.android.systemui.statusbar.policy.ConfigurationController
-import java.io.PrintWriter
-import java.util.Locale
-import java.util.TimeZone
-import javax.inject.Inject
-
-/**
- * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
- * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
- */
-class ClockEventController @Inject constructor(
- private val statusBarStateController: StatusBarStateController,
- private val broadcastDispatcher: BroadcastDispatcher,
- private val batteryController: BatteryController,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val configurationController: ConfigurationController,
- @Main private val resources: Resources,
- private val context: Context
-) {
- var clock: Clock? = null
- set(value) {
- field = value
- if (value != null) {
- value.initialize(resources, dozeAmount, 0f)
- }
- }
-
- private var isDozing = false
- private set
-
- private var isCharging = false
- private var dozeAmount = 0f
- private var isKeyguardShowing = false
-
- private val configListener = object : ConfigurationController.ConfigurationListener {
- override fun onThemeChanged() {
- clock?.events?.onColorPaletteChanged(resources)
- }
- }
-
- private val batteryCallback = object : BatteryStateChangeCallback {
- override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
- if (isKeyguardShowing && !isCharging && charging) {
- clock?.animations?.charge()
- }
- isCharging = charging
- }
- }
-
- private val localeBroadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- clock?.events?.onLocaleChanged(Locale.getDefault())
- }
- }
-
- private val statusBarStateListener = object : StatusBarStateController.StateListener {
- override fun onDozeAmountChanged(linear: Float, eased: Float) {
- clock?.animations?.doze(linear)
-
- isDozing = linear > dozeAmount
- dozeAmount = linear
- }
- }
-
- private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
- override fun onKeyguardVisibilityChanged(showing: Boolean) {
- isKeyguardShowing = showing
- if (!isKeyguardShowing) {
- clock?.animations?.doze(if (isDozing) 1f else 0f)
- }
- }
-
- override fun onTimeFormatChanged(timeFormat: String) {
- clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
- }
-
- override fun onTimeZoneChanged(timeZone: TimeZone) {
- clock?.events?.onTimeZoneChanged(timeZone)
- }
-
- override fun onUserSwitchComplete(userId: Int) {
- clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
- }
- }
-
- init {
- isDozing = statusBarStateController.isDozing
- }
-
- fun registerListeners() {
- dozeAmount = statusBarStateController.dozeAmount
- isDozing = statusBarStateController.isDozing || dozeAmount != 0f
-
- broadcastDispatcher.registerReceiver(
- localeBroadcastReceiver,
- IntentFilter(Intent.ACTION_LOCALE_CHANGED)
- )
- configurationController.addCallback(configListener)
- batteryController.addCallback(batteryCallback)
- keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
- statusBarStateController.addCallback(statusBarStateListener)
- }
-
- fun unregisterListeners() {
- broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver)
- configurationController.removeCallback(configListener)
- batteryController.removeCallback(batteryCallback)
- keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
- statusBarStateController.removeCallback(statusBarStateListener)
- }
-
- /**
- * Dump information for debugging
- */
- fun dump(pw: PrintWriter) {
- pw.println(this)
- clock?.dump(pw)
- }
-
- companion object {
- private val TAG = ClockEventController::class.simpleName
- private const val FORMAT_NUMBER = 1234567890
- }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index e1fabde..206b8be 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -5,8 +5,10 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
import android.util.AttributeSet;
-import android.util.Log;
+import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -15,14 +17,19 @@
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.dagger.KeyguardStatusViewScope;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.plugins.Clock;
+import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.shared.clocks.AnimatableClockView;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.TimeZone;
+
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
@@ -43,10 +50,17 @@
public static final int SMALL = 1;
/**
+ * Optional/alternative clock injected via plugin.
+ */
+ private ClockPlugin mClockPlugin;
+
+ /**
* Frame for small/large clocks
*/
- private FrameLayout mSmallClockFrame;
+ private FrameLayout mClockFrame;
private FrameLayout mLargeClockFrame;
+ private AnimatableClockView mClockView;
+ private AnimatableClockView mLargeClockView;
private View mStatusArea;
private int mSmartspaceTopOffset;
@@ -66,6 +80,12 @@
@VisibleForTesting AnimatorSet mClockOutAnim = null;
private ObjectAnimator mStatusAreaAnim = null;
+ /**
+ * If the Keyguard Slice has a header (big center-aligned text.)
+ */
+ private boolean mSupportsDarkText;
+ private int[] mColorPalette;
+
private int mClockSwitchYAmount;
@VisibleForTesting boolean mChildrenAreLaidOut = false;
@@ -77,38 +97,97 @@
* Apply dp changes on font/scale change
*/
public void onDensityOrFontScaleChanged() {
+ mLargeClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources()
+ .getDimensionPixelSize(R.dimen.large_clock_text_size));
+ mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources()
+ .getDimensionPixelSize(R.dimen.clock_text_size));
+
mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_clock_switch_y_shift);
+
mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_smartspace_top_offset);
}
+ /**
+ * Returns if this view is presenting a custom clock, or the default implementation.
+ */
+ public boolean hasCustomClock() {
+ return mClockPlugin != null;
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
+ mClockFrame = findViewById(R.id.lockscreen_clock_view);
+ mClockView = findViewById(R.id.animatable_clock_view);
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
+ mLargeClockView = findViewById(R.id.animatable_clock_view_large);
mStatusArea = findViewById(R.id.keyguard_status_area);
onDensityOrFontScaleChanged();
}
- void setClock(Clock clock, int statusBarState) {
+ void setClockPlugin(ClockPlugin plugin, int statusBarState) {
// Disconnect from existing plugin.
- mSmallClockFrame.removeAllViews();
- mLargeClockFrame.removeAllViews();
-
- if (clock == null) {
- Log.e(TAG, "No clock being shown");
+ if (mClockPlugin != null) {
+ View smallClockView = mClockPlugin.getView();
+ if (smallClockView != null && smallClockView.getParent() == mClockFrame) {
+ mClockFrame.removeView(smallClockView);
+ }
+ View bigClockView = mClockPlugin.getBigClockView();
+ if (bigClockView != null && bigClockView.getParent() == mLargeClockFrame) {
+ mLargeClockFrame.removeView(bigClockView);
+ }
+ mClockPlugin.onDestroyView();
+ mClockPlugin = null;
+ }
+ if (plugin == null) {
+ mClockView.setVisibility(View.VISIBLE);
+ mLargeClockView.setVisibility(View.VISIBLE);
return;
}
-
// Attach small and big clock views to hierarchy.
- mSmallClockFrame.addView(clock.getSmallClock(), -1,
- new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- mLargeClockFrame.addView(clock.getLargeClock());
+ View smallClockView = plugin.getView();
+ if (smallClockView != null) {
+ mClockFrame.addView(smallClockView, -1,
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ mClockView.setVisibility(View.GONE);
+ }
+ View bigClockView = plugin.getBigClockView();
+ if (bigClockView != null) {
+ mLargeClockFrame.addView(bigClockView);
+ mLargeClockView.setVisibility(View.GONE);
+ }
+
+ // Initialize plugin parameters.
+ mClockPlugin = plugin;
+ mClockPlugin.setStyle(getPaint().getStyle());
+ mClockPlugin.setTextColor(getCurrentTextColor());
+ mClockPlugin.setDarkAmount(mDarkAmount);
+ if (mColorPalette != null) {
+ mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
+ }
+ }
+
+ /**
+ * It will also update plugin setStyle if plugin is connected.
+ */
+ public void setStyle(Style style) {
+ if (mClockPlugin != null) {
+ mClockPlugin.setStyle(style);
+ }
+ }
+
+ /**
+ * It will also update plugin setTextColor if plugin is connected.
+ */
+ public void setTextColor(int color) {
+ if (mClockPlugin != null) {
+ mClockPlugin.setTextColor(color);
+ }
}
private void updateClockViews(boolean useLargeClock, boolean animate) {
@@ -124,14 +203,14 @@
int direction = 1;
float statusAreaYTranslation;
if (useLargeClock) {
- out = mSmallClockFrame;
+ out = mClockFrame;
in = mLargeClockFrame;
if (indexOfChild(in) == -1) addView(in);
direction = -1;
- statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop()
+ statusAreaYTranslation = mClockFrame.getTop() - mStatusArea.getTop()
+ mSmartspaceTopOffset;
} else {
- in = mSmallClockFrame;
+ in = mClockFrame;
out = mLargeClockFrame;
statusAreaYTranslation = 0f;
@@ -190,6 +269,18 @@
}
/**
+ * Set the amount (ratio) that the device has transitioned to doze.
+ *
+ * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+ */
+ public void setDarkAmount(float darkAmount) {
+ mDarkAmount = darkAmount;
+ if (mClockPlugin != null) {
+ mClockPlugin.setDarkAmount(darkAmount);
+ }
+ }
+
+ /**
* Display the desired clock and hide the other one
*
* @return true if desired clock appeared and false if it was already visible
@@ -220,11 +311,64 @@
mChildrenAreLaidOut = true;
}
+ public Paint getPaint() {
+ return mClockView.getPaint();
+ }
+
+ public int getCurrentTextColor() {
+ return mClockView.getCurrentTextColor();
+ }
+
+ public float getTextSize() {
+ return mClockView.getTextSize();
+ }
+
+ /**
+ * Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm.
+ */
+ public void refresh() {
+ if (mClockPlugin != null) {
+ mClockPlugin.onTimeTick();
+ }
+ }
+
+ /**
+ * Notifies that the time zone has changed.
+ */
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ if (mClockPlugin != null) {
+ mClockPlugin.onTimeZoneChanged(timeZone);
+ }
+ }
+
+ /**
+ * Notifies that the time format has changed.
+ *
+ * @param timeFormat "12" for 12-hour format, "24" for 24-hour format
+ */
+ public void onTimeFormatChanged(String timeFormat) {
+ if (mClockPlugin != null) {
+ mClockPlugin.onTimeFormatChanged(timeFormat);
+ }
+ }
+
+ void updateColors(ColorExtractor.GradientColors colors) {
+ mSupportsDarkText = colors.supportsDarkText();
+ mColorPalette = colors.getColorPalette();
+ if (mClockPlugin != null) {
+ mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
+ }
+ }
+
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardClockSwitch:");
- pw.println(" mClockFrame: " + mSmallClockFrame);
+ pw.println(" mClockPlugin: " + mClockPlugin);
+ pw.println(" mClockFrame: " + mClockFrame);
pw.println(" mLargeClockFrame: " + mLargeClockFrame);
pw.println(" mStatusArea: " + mStatusArea);
+ pw.println(" mDarkAmount: " + mDarkAmount);
+ pw.println(" mSupportsDarkText: " + mSupportsDarkText);
+ pw.println(" mColorPalette: " + Arrays.toString(mColorPalette));
pw.println(" mDisplayedClockSize: " + mDisplayedClockSize);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index dd78f1c..ad06e05 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -22,6 +22,8 @@
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+import android.app.WallpaperManager;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.UserHandle;
import android.provider.Settings;
@@ -30,30 +32,36 @@
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
+import com.android.internal.colorextraction.ColorExtractor;
+import com.android.keyguard.clock.ClockManager;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.plugins.Clock;
+import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.util.Locale;
+import java.util.TimeZone;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -63,24 +71,48 @@
*/
public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch>
implements Dumpable {
+ private static final boolean CUSTOM_CLOCKS_ENABLED = true;
+
private final StatusBarStateController mStatusBarStateController;
- private final ClockRegistry mClockRegistry;
+ private final SysuiColorExtractor mColorExtractor;
+ private final ClockManager mClockManager;
private final KeyguardSliceViewController mKeyguardSliceViewController;
private final NotificationIconAreaController mNotificationIconAreaController;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final BatteryController mBatteryController;
private final LockscreenSmartspaceController mSmartspaceController;
+ private final Resources mResources;
private final SecureSettings mSecureSettings;
private final DumpManager mDumpManager;
- private final ClockEventController mClockEventController;
- /** Clock frames for both small and large sizes */
- private FrameLayout mSmallClockFrame; // top aligned clock
+ /**
+ * Clock for both small and large sizes
+ */
+ private AnimatableClockController mClockViewController;
+ private FrameLayout mClockFrame; // top aligned clock
+ private AnimatableClockController mLargeClockViewController;
private FrameLayout mLargeClockFrame; // centered clock
@KeyguardClockSwitch.ClockSize
private int mCurrentClockSize = SMALL;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
private int mKeyguardClockTopMargin = 0;
- private final ClockRegistry.ClockChangeListener mClockChangedListener;
+
+ /**
+ * Listener for changes to the color palette.
+ *
+ * The color palette changes when the wallpaper is changed.
+ */
+ private final ColorExtractor.OnColorsChangedListener mColorsListener =
+ (extractor, which) -> {
+ if ((which & WallpaperManager.FLAG_LOCK) != 0) {
+ mView.updateColors(getGradientColors());
+ }
+ };
+
+ private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
private ViewGroup mStatusArea;
// If set will replace keyguard_slice_view
@@ -89,9 +121,9 @@
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private boolean mOnlyClock = false;
- private final Executor mUiExecutor;
+ private Executor mUiExecutor;
private boolean mCanShowDoubleLineClock = true;
- private final ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
+ private ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
@Override
public void onChange(boolean change) {
updateDoubleLineClock();
@@ -112,32 +144,34 @@
public KeyguardClockSwitchController(
KeyguardClockSwitch keyguardClockSwitch,
StatusBarStateController statusBarStateController,
- ClockRegistry clockRegistry,
+ SysuiColorExtractor colorExtractor,
+ ClockManager clockManager,
KeyguardSliceViewController keyguardSliceViewController,
NotificationIconAreaController notificationIconAreaController,
+ BroadcastDispatcher broadcastDispatcher,
+ BatteryController batteryController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
LockscreenSmartspaceController smartspaceController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
SecureSettings secureSettings,
@Main Executor uiExecutor,
- DumpManager dumpManager,
- ClockEventController clockEventController,
- FeatureFlags featureFlags) {
+ @Main Resources resources,
+ DumpManager dumpManager) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
- mClockRegistry = clockRegistry;
+ mColorExtractor = colorExtractor;
+ mClockManager = clockManager;
mKeyguardSliceViewController = keyguardSliceViewController;
mNotificationIconAreaController = notificationIconAreaController;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mBatteryController = batteryController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mSmartspaceController = smartspaceController;
+ mResources = resources;
mSecureSettings = secureSettings;
mUiExecutor = uiExecutor;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mDumpManager = dumpManager;
- mClockEventController = clockEventController;
-
- mClockRegistry.setEnabled(featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS));
- mClockChangedListener = () -> {
- setClock(mClockRegistry.createCurrentClock());
- };
}
/**
@@ -154,18 +188,40 @@
public void onInit() {
mKeyguardSliceViewController.init();
- mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
+ mClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
+ mClockViewController =
+ new AnimatableClockController(
+ mView.findViewById(R.id.animatable_clock_view),
+ mStatusBarStateController,
+ mBroadcastDispatcher,
+ mBatteryController,
+ mKeyguardUpdateMonitor,
+ mResources);
+ mClockViewController.init();
+
+ mLargeClockViewController =
+ new AnimatableClockController(
+ mView.findViewById(R.id.animatable_clock_view_large),
+ mStatusBarStateController,
+ mBroadcastDispatcher,
+ mBatteryController,
+ mKeyguardUpdateMonitor,
+ mResources);
+ mLargeClockViewController.init();
+
mDumpManager.unregisterDumpable(getClass().toString()); // unregister previous clocks
mDumpManager.registerDumpable(getClass().toString(), this);
}
@Override
protected void onViewAttached() {
- mClockRegistry.registerClockChangeListener(mClockChangedListener);
- setClock(mClockRegistry.createCurrentClock());
- mClockEventController.registerListeners();
+ if (CUSTOM_CLOCKS_ENABLED) {
+ mClockManager.addOnClockChangedListener(mClockChangedListener);
+ }
+ mColorExtractor.addOnColorsChangedListener(mColorsListener);
+ mView.updateColors(getGradientColors());
mKeyguardClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
@@ -188,6 +244,7 @@
ksv.setVisibility(View.GONE);
addSmartspaceView(ksvIndex);
+ updateClockLayout();
}
mSecureSettings.registerContentObserverForUser(
@@ -209,9 +266,11 @@
@Override
protected void onViewDetached() {
- mClockRegistry.unregisterClockChangeListener(mClockChangedListener);
- mClockEventController.unregisterListeners();
- setClock(null);
+ if (CUSTOM_CLOCKS_ENABLED) {
+ mClockManager.removeOnClockChangedListener(mClockChangedListener);
+ }
+ mColorExtractor.removeOnColorsChangedListener(mColorsListener);
+ mView.setClockPlugin(null, mStatusBarStateController.getState());
mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
@@ -250,6 +309,18 @@
mView.onDensityOrFontScaleChanged();
mKeyguardClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
+
+ updateClockLayout();
+ }
+
+ private void updateClockLayout() {
+ int largeClockTopMargin = getContext().getResources().getDimensionPixelSize(
+ R.dimen.keyguard_large_clock_top_margin)
+ - (int) mLargeClockViewController.getBottom();
+ RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
+ MATCH_PARENT);
+ lp.topMargin = largeClockTopMargin;
+ mLargeClockFrame.setLayoutParams(lp);
}
/**
@@ -263,34 +334,46 @@
mCurrentClockSize = clockSize;
- Clock clock = getClock();
boolean appeared = mView.switchToClock(clockSize, animate);
- if (clock != null && animate && appeared && clockSize == LARGE) {
- clock.getAnimations().enter();
+ if (animate && appeared && clockSize == LARGE) {
+ mLargeClockViewController.animateAppear();
+ }
+ }
+
+ public void animateFoldToAod() {
+ if (mClockViewController != null) {
+ mClockViewController.animateFoldAppear();
+ mLargeClockViewController.animateFoldAppear();
}
}
/**
- * Animates the clock view between folded and unfolded states
+ * If we're presenting a custom clock of just the default one.
*/
- public void animateFoldToAod(float foldFraction) {
- Clock clock = getClock();
- if (clock != null) {
- clock.getAnimations().fold(foldFraction);
- }
+ public boolean hasCustomClock() {
+ return mView.hasCustomClock();
+ }
+
+ /**
+ * Get the clock text size.
+ */
+ public float getClockTextSize() {
+ return mView.getTextSize();
}
/**
* Refresh clock. Called in response to TIME_TICK broadcasts.
*/
void refresh() {
+ if (mClockViewController != null) {
+ mClockViewController.refreshTime();
+ mLargeClockViewController.refreshTime();
+ }
if (mSmartspaceController != null) {
mSmartspaceController.requestSmartspaceUpdate();
}
- Clock clock = getClock();
- if (clock != null) {
- clock.getEvents().onTimeTick();
- }
+
+ mView.refresh();
}
/**
@@ -302,7 +385,7 @@
void updatePosition(int x, float scale, AnimationProperties props, boolean animate) {
x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x;
- PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X,
+ PropertyAnimator.setProperty(mClockFrame, AnimatableProperty.TRANSLATION_X,
x, props, animate);
PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
scale, props, animate);
@@ -315,39 +398,25 @@
}
}
+ void updateTimeZone(TimeZone timeZone) {
+ mView.onTimeZoneChanged(timeZone);
+ }
+
/**
* Get y-bottom position of the currently visible clock on the keyguard.
* We can't directly getBottom() because clock changes positions in AOD for burn-in
*/
int getClockBottom(int statusBarHeaderHeight) {
- Clock clock = getClock();
- if (clock == null) {
- return 0;
- }
-
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
+ View clock = mLargeClockFrame.findViewById(
+ com.android.systemui.R.id.animatable_clock_view_large);
int frameHeight = mLargeClockFrame.getHeight();
- int clockHeight = clock.getLargeClock().getHeight();
+ int clockHeight = clock.getHeight();
return frameHeight / 2 + clockHeight / 2;
} else {
- int clockHeight = clock.getSmallClock().getHeight();
- return clockHeight + statusBarHeaderHeight + mKeyguardClockTopMargin;
- }
- }
-
- /**
- * Get the height of the currently visible clock on the keyguard.
- */
- int getClockHeight() {
- Clock clock = getClock();
- if (clock == null) {
- return 0;
- }
-
- if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
- return clock.getLargeClock().getHeight();
- } else {
- return clock.getSmallClock().getHeight();
+ return mClockFrame.findViewById(
+ com.android.systemui.R.id.animatable_clock_view).getHeight()
+ + statusBarHeaderHeight + mKeyguardClockTopMargin;
}
}
@@ -362,13 +431,12 @@
mNotificationIconAreaController.setupAodIcons(nic);
}
- private void setClock(Clock clock) {
- mClockEventController.setClock(clock);
- mView.setClock(clock, mStatusBarStateController.getState());
+ private void setClockPlugin(ClockPlugin plugin) {
+ mView.setClockPlugin(plugin, mStatusBarStateController.getState());
}
- private Clock getClock() {
- return mClockEventController.getClock();
+ private ColorExtractor.GradientColors getGradientColors() {
+ return mColorExtractor.getColors(WallpaperManager.FLAG_LOCK);
}
private int getCurrentLayoutDirection() {
@@ -401,10 +469,8 @@
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("currentClockSizeLarge=" + (mCurrentClockSize == LARGE));
pw.println("mCanShowDoubleLineClock=" + mCanShowDoubleLineClock);
- Clock clock = getClock();
- if (clock != null) {
- clock.dump(pw);
- }
+ mClockViewController.dump(pw);
+ mLargeClockViewController.dump(pw);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 83e23bd..cb3172d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -17,11 +17,14 @@
package com.android.keyguard;
import android.content.Context;
+import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridLayout;
+import androidx.core.graphics.ColorUtils;
+
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -43,6 +46,7 @@
private View mMediaHostContainer;
private float mDarkAmount = 0;
+ private int mTextColor;
public KeyguardStatusView(Context context) {
this(context, null, 0);
@@ -67,6 +71,7 @@
}
mKeyguardSlice = findViewById(R.id.keyguard_slice_view);
+ mTextColor = mClockView.getCurrentTextColor();
mMediaHostContainer = findViewById(R.id.status_view_media_container);
@@ -78,12 +83,15 @@
return;
}
mDarkAmount = darkAmount;
+ mClockView.setDarkAmount(darkAmount);
CrossFadeHelper.fadeOut(mMediaHostContainer, darkAmount);
updateDark();
}
void updateDark() {
+ final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
mKeyguardSlice.setDarkAmount(mDarkAmount);
+ mClockView.setTextColor(blendedTextColor);
}
/** Sets a translationY value on every child view except for the media view. */
@@ -105,6 +113,7 @@
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardStatusView:");
pw.println(" mDarkAmount: " + mDarkAmount);
+ pw.println(" mTextColor: " + Integer.toHexString(mTextColor));
if (mClockView != null) {
mClockView.dump(pw, args);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c715a4e..014d082 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -30,6 +30,8 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ViewController;
+import java.util.TimeZone;
+
import javax.inject.Inject;
/**
@@ -94,6 +96,13 @@
}
/**
+ * The amount we're in doze.
+ */
+ public void setDarkAmount(float darkAmount) {
+ mView.setDarkAmount(darkAmount);
+ }
+
+ /**
* Set which clock should be displayed on the keyguard. The other one will be automatically
* hidden.
*/
@@ -105,10 +114,16 @@
* Performs fold to aod animation of the clocks (changes font weight from bold to thin).
* This animation is played when AOD is enabled and foldable device is fully folded, it is
* displayed on the outer screen
- * @param foldFraction current fraction of fold animation complete
*/
- public void animateFoldToAod(float foldFraction) {
- mKeyguardClockSwitchController.animateFoldToAod(foldFraction);
+ public void animateFoldToAod() {
+ mKeyguardClockSwitchController.animateFoldToAod();
+ }
+
+ /**
+ * If we're presenting a custom clock of just the default one.
+ */
+ public boolean hasCustomClock() {
+ return mKeyguardClockSwitchController.hasCustomClock();
}
/**
@@ -128,11 +143,24 @@
}
/**
- * Update the pivot position based on the parent view
+ * Set pivot x.
*/
- public void updatePivot(float parentWidth, float parentHeight) {
- mView.setPivotX(parentWidth / 2f);
- mView.setPivotY(mKeyguardClockSwitchController.getClockHeight() / 2f);
+ public void setPivotX(float pivot) {
+ mView.setPivotX(pivot);
+ }
+
+ /**
+ * Set pivot y.
+ */
+ public void setPivotY(float pivot) {
+ mView.setPivotY(pivot);
+ }
+
+ /**
+ * Get the clock text size.
+ */
+ public float getClockTextSize() {
+ return mKeyguardClockSwitchController.getClockTextSize();
}
/**
@@ -212,6 +240,11 @@
}
@Override
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mKeyguardClockSwitchController.updateTimeZone(timeZone);
+ }
+
+ @Override
public void onKeyguardVisibilityChanged(boolean showing) {
if (showing) {
if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index fef7383..1c57480 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -75,12 +75,12 @@
}
private var radius: Float = 0f
set(value) {
- rippleShader.radius = value
+ rippleShader.setMaxSize(value * 2f, value * 2f)
field = value
}
private var origin: PointF = PointF()
set(value) {
- rippleShader.origin = value
+ rippleShader.setCenter(value.x, value.y)
field = value
}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 8292e52..da675de 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.content.res.Configuration
import android.graphics.PixelFormat
-import android.graphics.PointF
import android.os.SystemProperties
import android.util.DisplayMetrics
import android.view.View
@@ -85,7 +84,7 @@
private var debounceLevel = 0
@VisibleForTesting
- var rippleView: RippleView = RippleView(context, attrs = null)
+ var rippleView: RippleView = RippleView(context, attrs = null).also { it.setupShader() }
init {
pluggedIn = batteryController.isPluggedIn
@@ -177,20 +176,25 @@
context.display.getRealMetrics(displayMetrics)
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
- rippleView.radius = Integer.max(width, height).toFloat()
- rippleView.origin = when (RotationUtils.getExactRotation(context)) {
+ val maxDiameter = Integer.max(width, height) * 2f
+ rippleView.setMaxSize(maxDiameter, maxDiameter)
+ when (RotationUtils.getExactRotation(context)) {
RotationUtils.ROTATION_LANDSCAPE -> {
- PointF(width * normalizedPortPosY, height * (1 - normalizedPortPosX))
+ rippleView.setCenter(
+ width * normalizedPortPosY, height * (1 - normalizedPortPosX))
}
RotationUtils.ROTATION_UPSIDE_DOWN -> {
- PointF(width * (1 - normalizedPortPosX), height * (1 - normalizedPortPosY))
+ rippleView.setCenter(
+ width * (1 - normalizedPortPosX), height * (1 - normalizedPortPosY))
}
RotationUtils.ROTATION_SEASCAPE -> {
- PointF(width * (1 - normalizedPortPosY), height * normalizedPortPosX)
+ rippleView.setCenter(
+ width * (1 - normalizedPortPosY), height * normalizedPortPosX)
}
else -> {
// ROTATION_NONE
- PointF(width * normalizedPortPosX, height * normalizedPortPosY)
+ rippleView.setCenter(
+ width * normalizedPortPosX, height * normalizedPortPosY)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index f6368ee..65400c2 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -21,7 +21,6 @@
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
-import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
@@ -34,6 +33,7 @@
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.ripple.RippleShader;
import com.android.systemui.ripple.RippleView;
import java.text.NumberFormat;
@@ -138,6 +138,8 @@
animatorSetScrim.start();
mRippleView = findViewById(R.id.wireless_charging_ripple);
+ // TODO: Make rounded box shape if the device is tablet.
+ mRippleView.setupShader(RippleShader.RippleShape.CIRCLE);
OnAttachStateChangeListener listener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View view) {
@@ -230,11 +232,11 @@
if (mRippleView != null) {
int width = getMeasuredWidth();
int height = getMeasuredHeight();
- mRippleView.setColor(
- Utils.getColorAttr(mRippleView.getContext(),
- android.R.attr.colorAccent).getDefaultColor());
- mRippleView.setOrigin(new PointF(width / 2, height / 2));
- mRippleView.setRadius(Math.max(width, height) * 0.5f);
+ mRippleView.setCenter(width * 0.5f, height * 0.5f);
+ float maxSize = Math.max(width, height);
+ mRippleView.setMaxSize(maxSize, maxSize);
+ mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
+ android.R.attr.colorAccent).getDefaultColor());
}
super.onLayout(changed, left, top, right, bottom);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 733b1b6..d479988 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -145,9 +145,6 @@
/***************************************/
// 600- status bar
- public static final BooleanFlag COMBINED_STATUS_BAR_SIGNAL_ICONS =
- new BooleanFlag(601, false);
-
public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip);
@@ -218,7 +215,11 @@
new SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false);
public static final BooleanFlag NEW_BACK_AFFORDANCE =
- new BooleanFlag(1203, false /* default */, true /* teamfood */);
+ new BooleanFlag(1203, false /* default */, false /* teamfood */);
+
+ // 1300 - screenshots
+
+ public static final BooleanFlag SCREENSHOT_REQUEST_PROCESSOR = new BooleanFlag(1300, false);
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 495f697..0f1ae00 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -19,7 +19,6 @@
import android.annotation.SuppressLint
import android.app.StatusBarManager
import android.content.Context
-import android.graphics.PointF
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
@@ -202,10 +201,10 @@
val height = windowBounds.height()
val width = windowBounds.width()
- rippleView.radius = height / 5f
+ val maxDiameter = height / 2.5f
+ rippleView.setMaxSize(maxDiameter, maxDiameter)
// Center the ripple on the bottom of the screen in the middle.
- rippleView.origin = PointF(/* x= */ width / 2f, /* y= */ height.toFloat())
-
+ rippleView.setCenter(width * 0.5f, height.toFloat())
val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
val colorWithAlpha = ColorUtils.setAlphaComponent(color, 70)
rippleView.setColor(colorWithAlpha)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index fed546bf..6a505f0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -23,10 +23,10 @@
/**
* An expanding ripple effect for the media tap-to-transfer receiver chip.
*/
-class ReceiverChipRippleView(
- context: Context?, attrs: AttributeSet?
-) : RippleView(context, attrs) {
+class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) {
init {
+ // TODO: use RippleShape#ELLIPSE when calling setupShader.
+ setupShader()
setRippleFill(true)
duration = 3000L
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 6bc50a6..da9fefa 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -48,6 +48,7 @@
import androidx.annotation.NonNull;
+import com.android.keyguard.KeyguardViewController;
import com.android.systemui.Dumpable;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -89,6 +90,7 @@
private final AccessibilityManager mAccessibilityManager;
private final Lazy<AssistManager> mAssistManagerLazy;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+ private final KeyguardViewController mKeyguardViewController;
private final UserTracker mUserTracker;
private final SystemActions mSystemActions;
private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
@@ -123,6 +125,7 @@
OverviewProxyService overviewProxyService,
Lazy<AssistManager> assistManagerLazy,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
+ KeyguardViewController keyguardViewController,
NavigationModeController navigationModeController,
UserTracker userTracker,
DumpManager dumpManager) {
@@ -131,6 +134,7 @@
mAccessibilityManager = accessibilityManager;
mAssistManagerLazy = assistManagerLazy;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+ mKeyguardViewController = keyguardViewController;
mUserTracker = userTracker;
mSystemActions = systemActions;
accessibilityManager.addAccessibilityServicesStateChangeListener(this);
@@ -317,8 +321,12 @@
* {@link InputMethodService} and the keyguard states.
*/
public boolean isImeShown(int vis) {
- View shadeWindowView = mCentralSurfacesOptionalLazy.get().get().getNotificationShadeWindowView();
- boolean isKeyguardShowing = mCentralSurfacesOptionalLazy.get().get().isKeyguardShowing();
+ View shadeWindowView = null;
+ if (mCentralSurfacesOptionalLazy.get().isPresent()) {
+ shadeWindowView =
+ mCentralSurfacesOptionalLazy.get().get().getNotificationShadeWindowView();
+ }
+ boolean isKeyguardShowing = mKeyguardViewController.isShowing();
boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow()
&& shadeWindowView.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
return imeVisibleOnShade
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index b05e75e..b44b4de 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -510,7 +510,6 @@
private fun playCommitBackAnimation() {
// Check if we should vibrate again
if (previousState != GestureState.FLUNG) {
- backCallback.triggerBack()
velocityTracker!!.computeCurrentVelocity(1000)
val isSlow = abs(velocityTracker!!.xVelocity) < 500
val hasNotVibratedRecently =
@@ -519,6 +518,10 @@
vibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK)
}
}
+ // Dispatch the actual back trigger
+ if (DEBUG) Log.d(TAG, "playCommitBackAnimation() invoked triggerBack() on backCallback")
+ backCallback.triggerBack()
+
playAnimation(setGoneEndListener)
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 057ed24..fc6dcd3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -60,6 +60,7 @@
import com.android.internal.util.LatencyTracker;
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.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -82,6 +83,7 @@
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
+import com.android.systemui.util.Assert;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
@@ -191,6 +193,7 @@
private final int mDisplayId;
private final Executor mMainExecutor;
+ private final Executor mBackgroundExecutor;
private final Rect mPipExcludedBounds = new Rect();
private final Rect mNavBarOverlayExcludedBounds = new Rect();
@@ -251,6 +254,7 @@
private BackGestureTfClassifierProvider mBackGestureTfClassifierProvider;
private Map<String, Integer> mVocab;
private boolean mUseMLModel;
+ private boolean mMLModelIsLoading;
// minimum width below which we do not run the model
private int mMLEnableWidth;
private float mMLModelThreshold;
@@ -318,6 +322,7 @@
SysUiState sysUiState,
PluginManager pluginManager,
@Main Executor executor,
+ @Background Executor backgroundExecutor,
BroadcastDispatcher broadcastDispatcher,
ProtoTracer protoTracer,
NavigationModeController navigationModeController,
@@ -334,6 +339,7 @@
mContext = context;
mDisplayId = context.getDisplayId();
mMainExecutor = executor;
+ mBackgroundExecutor = backgroundExecutor;
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
mPluginManager = pluginManager;
@@ -631,28 +637,63 @@
return;
}
- if (newState) {
- mBackGestureTfClassifierProvider = mBackGestureTfClassifierProviderProvider.get();
- mMLModelThreshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f);
- if (mBackGestureTfClassifierProvider.isActive()) {
- Trace.beginSection("EdgeBackGestureHandler#loadVocab");
- mVocab = mBackGestureTfClassifierProvider.loadVocab(mContext.getAssets());
- Trace.endSection();
- mUseMLModel = true;
+ mUseMLModel = newState;
+
+ if (mUseMLModel) {
+ Assert.isMainThread();
+ if (mMLModelIsLoading) {
+ Log.d(TAG, "Model tried to load while already loading.");
return;
}
- }
-
- mUseMLModel = false;
- if (mBackGestureTfClassifierProvider != null) {
+ mMLModelIsLoading = true;
+ mBackgroundExecutor.execute(() -> loadMLModel());
+ } else if (mBackGestureTfClassifierProvider != null) {
mBackGestureTfClassifierProvider.release();
mBackGestureTfClassifierProvider = null;
+ mVocab = null;
}
}
+ private void loadMLModel() {
+ BackGestureTfClassifierProvider provider = mBackGestureTfClassifierProviderProvider.get();
+ float threshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f);
+ Map<String, Integer> vocab = null;
+ if (provider != null && !provider.isActive()) {
+ provider.release();
+ provider = null;
+ Log.w(TAG, "Cannot load model because it isn't active");
+ }
+ if (provider != null) {
+ Trace.beginSection("EdgeBackGestureHandler#loadVocab");
+ vocab = provider.loadVocab(mContext.getAssets());
+ Trace.endSection();
+ }
+ BackGestureTfClassifierProvider finalProvider = provider;
+ Map<String, Integer> finalVocab = vocab;
+ mMainExecutor.execute(() -> onMLModelLoadFinished(finalProvider, finalVocab, threshold));
+ }
+
+ private void onMLModelLoadFinished(BackGestureTfClassifierProvider provider,
+ Map<String, Integer> vocab, float threshold) {
+ Assert.isMainThread();
+ mMLModelIsLoading = false;
+ if (!mUseMLModel) {
+ // This can happen if the user disables Gesture Nav while the model is loading.
+ if (provider != null) {
+ provider.release();
+ }
+ Log.d(TAG, "Model finished loading but isn't needed.");
+ return;
+ }
+ mBackGestureTfClassifierProvider = provider;
+ mVocab = vocab;
+ mMLModelThreshold = threshold;
+ }
+
private int getBackGesturePredictionsCategory(int x, int y, int app) {
- if (app == -1) {
+ BackGestureTfClassifierProvider provider = mBackGestureTfClassifierProvider;
+ if (provider == null || app == -1) {
return -1;
}
int distanceFromEdge;
@@ -673,7 +714,7 @@
new long[]{(long) y},
};
- mMLResults = mBackGestureTfClassifierProvider.predict(featuresVector);
+ mMLResults = provider.predict(featuresVector);
if (mMLResults == -1) {
return -1;
}
@@ -1031,6 +1072,7 @@
private final SysUiState mSysUiState;
private final PluginManager mPluginManager;
private final Executor mExecutor;
+ private final Executor mBackgroundExecutor;
private final BroadcastDispatcher mBroadcastDispatcher;
private final ProtoTracer mProtoTracer;
private final NavigationModeController mNavigationModeController;
@@ -1050,6 +1092,7 @@
SysUiState sysUiState,
PluginManager pluginManager,
@Main Executor executor,
+ @Background Executor backgroundExecutor,
BroadcastDispatcher broadcastDispatcher,
ProtoTracer protoTracer,
NavigationModeController navigationModeController,
@@ -1067,6 +1110,7 @@
mSysUiState = sysUiState;
mPluginManager = pluginManager;
mExecutor = executor;
+ mBackgroundExecutor = backgroundExecutor;
mBroadcastDispatcher = broadcastDispatcher;
mProtoTracer = protoTracer;
mNavigationModeController = navigationModeController;
@@ -1089,6 +1133,7 @@
mSysUiState,
mPluginManager,
mExecutor,
+ mBackgroundExecutor,
mBroadcastDispatcher,
mProtoTracer,
mNavigationModeController,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index ec0d081..eeb1010 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -134,18 +134,9 @@
mQSCarrierGroupController
.setOnSingleCarrierChangedListener(mView::setIsSingleCarrier);
- List<String> rssiIgnoredSlots;
-
- if (mFeatureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
- rssiIgnoredSlots = List.of(
- getResources().getString(com.android.internal.R.string.status_bar_no_calling),
- getResources().getString(com.android.internal.R.string.status_bar_call_strength)
- );
- } else {
- rssiIgnoredSlots = List.of(
- getResources().getString(com.android.internal.R.string.status_bar_mobile)
- );
- }
+ List<String> rssiIgnoredSlots = List.of(
+ getResources().getString(com.android.internal.R.string.status_bar_mobile)
+ );
mView.onAttach(mIconManager, mQSExpansionPathInterpolator, rssiIgnoredSlots,
mInsetsProvider, mFeatureFlags.isEnabled(Flags.COMBINED_QS_HEADERS));
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt b/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt
index 2dac639..e925b54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt
@@ -27,7 +27,6 @@
@JvmField val contentDescription: String? = null,
@JvmField val typeContentDescription: String? = null,
@JvmField val roaming: Boolean = false,
- @JvmField val providerModelBehavior: Boolean = false
) {
/**
* Changes the visibility of this state by returning a copy with the visibility changed.
@@ -41,4 +40,4 @@
if (this.visible == visible) return this
else return copy(visible = visible)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index 592da65..703b95a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -45,7 +45,7 @@
private View mSpacer;
@Nullable
private CellSignalState mLastSignalState;
- private boolean mProviderModelInitialized = false;
+ private boolean mMobileSignalInitialized = false;
private boolean mIsSingleCarrier;
public QSCarrier(Context context) {
@@ -96,35 +96,25 @@
mMobileRoaming.setImageTintList(colorStateList);
mMobileSignal.setImageTintList(colorStateList);
- if (state.providerModelBehavior) {
- if (!mProviderModelInitialized) {
- mProviderModelInitialized = true;
- mMobileSignal.setImageDrawable(
- mContext.getDrawable(R.drawable.ic_qs_no_calling_sms));
- }
- mMobileSignal.setImageDrawable(mContext.getDrawable(state.mobileSignalIconId));
- mMobileSignal.setContentDescription(state.contentDescription);
- } else {
- if (!mProviderModelInitialized) {
- mProviderModelInitialized = true;
- mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
- }
- mMobileSignal.setImageLevel(state.mobileSignalIconId);
- StringBuilder contentDescription = new StringBuilder();
- if (state.contentDescription != null) {
- contentDescription.append(state.contentDescription).append(", ");
- }
- if (state.roaming) {
- contentDescription
- .append(mContext.getString(R.string.data_connection_roaming))
- .append(", ");
- }
- // TODO: show mobile data off/no internet text for 5 seconds before carrier text
- if (hasValidTypeContentDescription(state.typeContentDescription)) {
- contentDescription.append(state.typeContentDescription);
- }
- mMobileSignal.setContentDescription(contentDescription);
+ if (!mMobileSignalInitialized) {
+ mMobileSignalInitialized = true;
+ mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
}
+ mMobileSignal.setImageLevel(state.mobileSignalIconId);
+ StringBuilder contentDescription = new StringBuilder();
+ if (state.contentDescription != null) {
+ contentDescription.append(state.contentDescription).append(", ");
+ }
+ if (state.roaming) {
+ contentDescription
+ .append(mContext.getString(R.string.data_connection_roaming))
+ .append(", ");
+ }
+ // TODO: show mobile data off/no internet text for 5 seconds before carrier text
+ if (hasValidTypeContentDescription(state.typeContentDescription)) {
+ contentDescription.append(state.typeContentDescription);
+ }
+ mMobileSignal.setContentDescription(contentDescription);
}
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index 209d09d..6a8bf75 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -42,10 +42,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.SignalCallback;
@@ -78,7 +75,6 @@
private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS];
private int[] mLastSignalLevel = new int[SIM_SLOTS];
private String[] mLastSignalLevelDescription = new String[SIM_SLOTS];
- private final boolean mProviderModel;
private final CarrierConfigTracker mCarrierConfigTracker;
private boolean mIsSingleCarrier;
@@ -90,9 +86,6 @@
private final SignalCallback mSignalCallback = new SignalCallback() {
@Override
public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) {
- if (mProviderModel) {
- return;
- }
int slotIndex = getSlotIndex(indicators.subId);
if (slotIndex >= SIM_SLOTS) {
Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
@@ -107,91 +100,12 @@
indicators.statusIcon.icon,
indicators.statusIcon.contentDescription,
indicators.typeContentDescription.toString(),
- indicators.roaming,
- mProviderModel
+ indicators.roaming
);
mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
}
@Override
- public void setCallIndicator(@NonNull IconState statusIcon, int subId) {
- if (!mProviderModel) {
- return;
- }
- int slotIndex = getSlotIndex(subId);
- if (slotIndex >= SIM_SLOTS) {
- Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
- return;
- }
- if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
- Log.e(TAG, "Invalid SIM slot index for subscription: " + subId);
- return;
- }
-
- boolean displayCallStrengthIcon =
- mCarrierConfigTracker.getCallStrengthConfig(subId);
-
- if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
- if (statusIcon.visible) {
- mInfos[slotIndex] = new CellSignalState(
- true,
- statusIcon.icon,
- statusIcon.contentDescription,
- "",
- false,
- mProviderModel);
- } else {
- // Whenever the no Calling & SMS state is cleared, switched to the last
- // known call strength icon.
- if (displayCallStrengthIcon) {
- mInfos[slotIndex] = new CellSignalState(
- true,
- mLastSignalLevel[slotIndex],
- mLastSignalLevelDescription[slotIndex],
- "",
- false,
- mProviderModel);
- } else {
- mInfos[slotIndex] = new CellSignalState(
- true,
- R.drawable.ic_qs_sim_card,
- "",
- "",
- false,
- mProviderModel);
- }
- }
- mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
- } else {
- mLastSignalLevel[slotIndex] = statusIcon.icon;
- mLastSignalLevelDescription[slotIndex] = statusIcon.contentDescription;
- // Only Shows the call strength icon when the no Calling & SMS icon is not
- // shown.
- if (mInfos[slotIndex].mobileSignalIconId
- != R.drawable.ic_qs_no_calling_sms) {
- if (displayCallStrengthIcon) {
- mInfos[slotIndex] = new CellSignalState(
- true,
- statusIcon.icon,
- statusIcon.contentDescription,
- "",
- false,
- mProviderModel);
- } else {
- mInfos[slotIndex] = new CellSignalState(
- true,
- R.drawable.ic_qs_sim_card,
- "",
- "",
- false,
- mProviderModel);
- }
- mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
- }
- }
- }
-
- @Override
public void setNoSims(boolean hasNoSims, boolean simDetected) {
if (hasNoSims) {
for (int i = 0; i < SIM_SLOTS; i++) {
@@ -219,14 +133,8 @@
@Background Handler bgHandler, @Main Looper mainLooper,
NetworkController networkController,
CarrierTextManager.Builder carrierTextManagerBuilder, Context context,
- CarrierConfigTracker carrierConfigTracker, FeatureFlags featureFlags,
- SlotIndexResolver slotIndexResolver) {
+ CarrierConfigTracker carrierConfigTracker, SlotIndexResolver slotIndexResolver) {
- if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
- mProviderModel = true;
- } else {
- mProviderModel = false;
- }
mActivityStarter = activityStarter;
mBgHandler = bgHandler;
mNetworkController = networkController;
@@ -262,8 +170,7 @@
R.drawable.ic_qs_no_calling_sms,
context.getText(AccessibilityContentDescriptions.NO_CALLING).toString(),
"",
- false,
- mProviderModel);
+ false);
mLastSignalLevel[i] = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
mLastSignalLevelDescription[i] =
context.getText(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0])
@@ -351,8 +258,7 @@
for (int i = 0; i < SIM_SLOTS; i++) {
if (mInfos[i].visible
&& mInfos[i].mobileSignalIconId == R.drawable.ic_qs_sim_card) {
- mInfos[i] = new CellSignalState(true, R.drawable.ic_blank, "", "", false,
- mProviderModel);
+ mInfos[i] = new CellSignalState(true, R.drawable.ic_blank, "", "", false);
}
}
}
@@ -470,15 +376,13 @@
private final CarrierTextManager.Builder mCarrierTextControllerBuilder;
private final Context mContext;
private final CarrierConfigTracker mCarrierConfigTracker;
- private final FeatureFlags mFeatureFlags;
private final SlotIndexResolver mSlotIndexResolver;
@Inject
public Builder(ActivityStarter activityStarter, @Background Handler handler,
@Main Looper looper, NetworkController networkController,
CarrierTextManager.Builder carrierTextControllerBuilder, Context context,
- CarrierConfigTracker carrierConfigTracker, FeatureFlags featureFlags,
- SlotIndexResolver slotIndexResolver) {
+ CarrierConfigTracker carrierConfigTracker, SlotIndexResolver slotIndexResolver) {
mActivityStarter = activityStarter;
mHandler = handler;
mLooper = looper;
@@ -486,7 +390,6 @@
mCarrierTextControllerBuilder = carrierTextControllerBuilder;
mContext = context;
mCarrierConfigTracker = carrierConfigTracker;
- mFeatureFlags = featureFlags;
mSlotIndexResolver = slotIndexResolver;
}
@@ -498,7 +401,7 @@
public QSCarrierGroupController build() {
return new QSCarrierGroupController(mView, mActivityStarter, mHandler, mLooper,
mNetworkController, mCarrierTextControllerBuilder, mContext,
- mCarrierConfigTracker, mFeatureFlags, mSlotIndexResolver);
+ mCarrierConfigTracker, mSlotIndexResolver);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index f1fdae7..3c8775d0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -778,7 +778,8 @@
return;
}
- mTelephonyManager.setDataEnabled(enabled);
+ mTelephonyManager.setDataEnabledForReason(
+ TelephonyManager.DATA_ENABLED_REASON_USER, enabled);
if (disableOtherSubscriptions) {
final List<SubscriptionInfo> subInfoList =
mSubscriptionManager.getActiveSubscriptionInfoList();
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
index 93a2efc..0a8e6e2 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
@@ -20,92 +20,107 @@
import android.util.MathUtils
/**
- * Shader class that renders an expanding charging ripple effect. A charging ripple contains
- * three elements:
- * 1. an expanding filled circle that appears in the beginning and quickly fades away
+ * Shader class that renders an expanding ripple effect. The ripple contains three elements:
+ *
+ * 1. an expanding filled [RippleShape] that appears in the beginning and quickly fades away
* 2. an expanding ring that appears throughout the effect
* 3. an expanding ring-shaped area that reveals noise over #2.
*
+ * The ripple shader will be default to the circle shape if not specified.
+ *
* Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
*/
-class RippleShader internal constructor() : RuntimeShader(SHADER) {
+class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.CIRCLE) :
+ RuntimeShader(buildShader(rippleShape)) {
+
+ /** Shapes that the [RippleShader] supports. */
+ enum class RippleShape {
+ CIRCLE,
+ ROUNDED_BOX,
+ ELLIPSE
+ }
+
companion object {
- private const val SHADER_UNIFORMS = """uniform vec2 in_origin;
+ private const val SHADER_UNIFORMS = """uniform vec2 in_center;
+ uniform vec2 in_size;
uniform float in_progress;
- uniform float in_maxRadius;
+ uniform float in_cornerRadius;
+ uniform float in_thickness;
uniform float in_time;
uniform float in_distort_radial;
uniform float in_distort_xy;
- uniform float in_radius;
uniform float in_fadeSparkle;
- uniform float in_fadeCircle;
+ uniform float in_fadeFill;
uniform float in_fadeRing;
uniform float in_blur;
uniform float in_pixelDensity;
layout(color) uniform vec4 in_color;
uniform float in_sparkle_strength;"""
- private const val SHADER_LIB = """float triangleNoise(vec2 n) {
- n = fract(n * vec2(5.3987, 5.4421));
- n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
- float xy = n.x * n.y;
- return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
+
+ private const val SHADER_CIRCLE_MAIN = """vec4 main(vec2 p) {
+ vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
+ float radius = in_size.x * 0.5;
+ float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
+ float inside = soften(sdCircle(p_distorted-in_center, radius * 1.2), in_blur);
+ float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
+ * (1.-sparkleRing) * in_fadeSparkle;
+
+ float rippleInsideAlpha = (1.-inside) * in_fadeFill;
+ float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ vec4 ripple = in_color * rippleAlpha;
+ return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
+ }
+ """
+
+ private const val SHADER_ROUNDED_BOX_MAIN = """vec4 main(vec2 p) {
+ float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
+ in_thickness), in_blur);
+ float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
+ in_blur);
+ float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
+ * (1.-sparkleRing) * in_fadeSparkle;
+
+ float rippleInsideAlpha = (1.-inside) * in_fadeFill;
+ float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ vec4 ripple = in_color * rippleAlpha;
+ return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
+ }
+ """
+
+ private const val SHADER_ELLIPSE_MAIN = """vec4 main(vec2 p) {
+ vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
+
+ float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
+ float inside = soften(sdEllipse(p_distorted-in_center, in_size * 1.2), in_blur);
+ float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
+ * (1.-sparkleRing) * in_fadeSparkle;
+
+ float rippleInsideAlpha = (1.-inside) * in_fadeFill;
+ float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ vec4 ripple = in_color * rippleAlpha;
+ return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
+ }
+ """
+
+ private const val CIRCLE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.CIRCLE_SDF +
+ SHADER_CIRCLE_MAIN
+ private const val ROUNDED_BOX_SHADER = SHADER_UNIFORMS +
+ RippleShaderUtilLibrary.SHADER_LIB + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+ SdfShaderLibrary.ROUNDED_BOX_SDF + SHADER_ROUNDED_BOX_MAIN
+ private const val ELLIPSE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.ELLIPSE_SDF +
+ SHADER_ELLIPSE_MAIN
+
+ private fun buildShader(rippleShape: RippleShape): String =
+ when (rippleShape) {
+ RippleShape.CIRCLE -> CIRCLE_SHADER
+ RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
+ RippleShape.ELLIPSE -> ELLIPSE_SHADER
}
- const float PI = 3.1415926535897932384626;
-
- float threshold(float v, float l, float h) {
- return step(l, v) * (1.0 - step(h, v));
- }
-
- float sparkles(vec2 uv, float t) {
- float n = triangleNoise(uv);
- float s = 0.0;
- for (float i = 0; i < 4; i += 1) {
- float l = i * 0.01;
- float h = l + 0.1;
- float o = smoothstep(n - l, h, n);
- o *= abs(sin(PI * o * (t + 0.55 * i)));
- s += o;
- }
- return s;
- }
-
- float softCircle(vec2 uv, vec2 xy, float radius, float blur) {
- float blurHalf = blur * 0.5;
- float d = distance(uv, xy);
- return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);
- }
-
- float softRing(vec2 uv, vec2 xy, float radius, float blur) {
- float thickness_half = radius * 0.25;
- float circle_outer = softCircle(uv, xy, radius + thickness_half, blur);
- float circle_inner = softCircle(uv, xy, radius - thickness_half, blur);
- return circle_outer - circle_inner;
- }
-
- vec2 distort(vec2 p, vec2 origin, float time,
- float distort_amount_radial, float distort_amount_xy) {
- float2 distance = origin - p;
- float angle = atan(distance.y, distance.x);
- return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
- cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
- + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
- cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
- }"""
- private const val SHADER_MAIN = """vec4 main(vec2 p) {
- vec2 p_distorted = distort(p, in_origin, in_time, in_distort_radial,
- in_distort_xy);
-
- // Draw shapes
- float sparkleRing = softRing(p_distorted, in_origin, in_radius, in_blur);
- float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
- * sparkleRing * in_fadeSparkle;
- float circle = softCircle(p_distorted, in_origin, in_radius * 1.2, in_blur);
- float rippleAlpha = max(circle * in_fadeCircle,
- softRing(p_distorted, in_origin, in_radius, in_blur) * in_fadeRing) * 0.45;
- vec4 ripple = in_color * rippleAlpha;
- return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
- }"""
- private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN
private fun subProgress(start: Float, end: Float, progress: Float): Float {
val min = Math.min(start, end)
@@ -116,22 +131,18 @@
}
/**
- * Maximum radius of the ripple.
+ * Sets the center position of the ripple.
*/
- var radius: Float = 0.0f
- set(value) {
- field = value
- setFloatUniform("in_maxRadius", value)
- }
+ fun setCenter(x: Float, y: Float) {
+ setFloatUniform("in_center", x, y)
+ }
- /**
- * Origin coordinate of the ripple.
- */
- var origin: PointF = PointF()
- set(value) {
- field = value
- setFloatUniform("in_origin", value.x, value.y)
- }
+ /** Max width of the ripple. */
+ private var maxSize: PointF = PointF()
+ fun setMaxSize(width: Float, height: Float) {
+ maxSize.x = width
+ maxSize.y = height
+ }
/**
* Progress of the ripple. Float value between [0, 1].
@@ -140,20 +151,27 @@
set(value) {
field = value
setFloatUniform("in_progress", value)
- setFloatUniform("in_radius",
- (1 - (1 - value) * (1 - value) * (1 - value))* radius)
+ val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
+
+ setFloatUniform("in_size", /* width= */ maxSize.x * curvedProg,
+ /* height= */ maxSize.y * curvedProg)
+ setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
+ // radius should not exceed width and height values.
+ setFloatUniform("in_cornerRadius",
+ Math.min(maxSize.x, maxSize.y) * curvedProg)
+
setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
val fadeIn = subProgress(0f, 0.1f, value)
val fadeOutNoise = subProgress(0.4f, 1f, value)
var fadeOutRipple = 0f
- var fadeCircle = 0f
+ var fadeFill = 0f
if (!rippleFill) {
- fadeCircle = subProgress(0f, 0.2f, value)
+ fadeFill = subProgress(0f, 0.6f, value)
fadeOutRipple = subProgress(0.3f, 1f, value)
}
setFloatUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
- setFloatUniform("in_fadeCircle", 1 - fadeCircle)
+ setFloatUniform("in_fadeFill", 1 - fadeFill)
setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
}
@@ -169,7 +187,7 @@
/**
* A hex value representing the ripple color, in the format of ARGB
*/
- var color: Int = 0xffffff.toInt()
+ var color: Int = 0xffffff
set(value) {
field = value
setColorUniform("in_color", value)
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
new file mode 100644
index 0000000..0cacbc2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.ripple
+
+/** A common utility functions that are used for computing [RippleShader]. */
+class RippleShaderUtilLibrary {
+ companion object {
+ const val SHADER_LIB = """
+ float triangleNoise(vec2 n) {
+ n = fract(n * vec2(5.3987, 5.4421));
+ n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
+ float xy = n.x * n.y;
+ return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
+ }
+ const float PI = 3.1415926535897932384626;
+
+ float sparkles(vec2 uv, float t) {
+ float n = triangleNoise(uv);
+ float s = 0.0;
+ for (float i = 0; i < 4; i += 1) {
+ float l = i * 0.01;
+ float h = l + 0.1;
+ float o = smoothstep(n - l, h, n);
+ o *= abs(sin(PI * o * (t + 0.55 * i)));
+ s += o;
+ }
+ return s;
+ }
+
+ vec2 distort(vec2 p, float time, float distort_amount_radial,
+ float distort_amount_xy) {
+ float angle = atan(p.y, p.x);
+ return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
+ cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
+ + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
+ cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
+ }"""
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
index fc52464..83d9f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
@@ -23,39 +23,42 @@
import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Paint
-import android.graphics.PointF
import android.util.AttributeSet
import android.view.View
+import com.android.systemui.ripple.RippleShader.RippleShape
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
+private const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt()
/**
- * A generic expanding ripple effect. To trigger the ripple expansion, set [radius] and [origin],
- * then call [startRipple].
+ * A generic expanding ripple effect.
+ *
+ * Set up the shader with a desired [RippleShape] using [setupShader], [setMaxSize] and [setCenter],
+ * then call [startRipple] to trigger the ripple expansion.
*/
open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
- private val rippleShader = RippleShader()
- private val defaultColor: Int = 0xffffffff.toInt()
+
+ private lateinit var rippleShader: RippleShader
+ private lateinit var rippleShape: RippleShape
private val ripplePaint = Paint()
var rippleInProgress: Boolean = false
- var radius: Float = 0.0f
- set(value) {
- rippleShader.radius = value
- field = value
- }
- var origin: PointF = PointF()
- set(value) {
- rippleShader.origin = value
- field = value
- }
var duration: Long = 1750
- init {
- rippleShader.color = defaultColor
- rippleShader.progress = 0f
- rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
- ripplePaint.shader = rippleShader
+ private var maxWidth: Float = 0.0f
+ private var maxHeight: Float = 0.0f
+ fun setMaxSize(maxWidth: Float, maxHeight: Float) {
+ this.maxWidth = maxWidth
+ this.maxHeight = maxHeight
+ rippleShader.setMaxSize(maxWidth, maxHeight)
+ }
+
+ private var centerX: Float = 0.0f
+ private var centerY: Float = 0.0f
+ fun setCenter(x: Float, y: Float) {
+ this.centerX = x
+ this.centerY = y
+ rippleShader.setCenter(x, y)
}
override fun onConfigurationChanged(newConfig: Configuration?) {
@@ -68,6 +71,18 @@
super.onAttachedToWindow()
}
+ /** Initializes the shader. Must be called before [startRipple]. */
+ fun setupShader(rippleShape: RippleShape = RippleShape.CIRCLE) {
+ this.rippleShape = rippleShape
+ rippleShader = RippleShader(rippleShape)
+
+ rippleShader.color = RIPPLE_DEFAULT_COLOR
+ rippleShader.progress = 0f
+ rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
+
+ ripplePaint.shader = rippleShader
+ }
+
@JvmOverloads
fun startRipple(onAnimationEnd: Runnable? = null) {
if (rippleInProgress) {
@@ -113,11 +128,24 @@
// if it's unsupported.
return
}
- // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
- // the active effect area. Values here should be kept in sync with the
- // animation implementation in the ripple shader.
- val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * radius * 2
- canvas.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
+ // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the
+ // active effect area. Values here should be kept in sync with the animation implementation
+ // in the ripple shader.
+ if (rippleShape == RippleShape.CIRCLE) {
+ val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxWidth
+ canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
+ } else {
+ val maskWidth = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxWidth * 2
+ val maskHeight = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxHeight * 2
+ canvas.drawRect(
+ /* left= */ centerX - maskWidth,
+ /* top= */ centerY - maskHeight,
+ /* right= */ centerX + maskWidth,
+ /* bottom= */ centerY + maskHeight,
+ ripplePaint)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
new file mode 100644
index 0000000..7f26146
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 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.ripple
+
+/** Library class that contains 2D signed distance functions. */
+class SdfShaderLibrary {
+ companion object {
+ const val CIRCLE_SDF = """
+ float sdCircle(vec2 p, float r) {
+ return (length(p)-r) / r;
+ }
+
+ float circleRing(vec2 p, float radius) {
+ float thicknessHalf = radius * 0.25;
+
+ float outerCircle = sdCircle(p, radius + thicknessHalf);
+ float innerCircle = sdCircle(p, radius);
+
+ return subtract(outerCircle, innerCircle);
+ }
+ """
+
+ const val ROUNDED_BOX_SDF = """
+ float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) {
+ size *= 0.5;
+ cornerRadius *= 0.5;
+ vec2 d = abs(p)-size+cornerRadius;
+
+ float outside = length(max(d, 0.0));
+ float inside = min(max(d.x, d.y), 0.0);
+
+ return (outside+inside-cornerRadius)/size.y;
+ }
+
+ float roundedBoxRing(vec2 p, vec2 size, float cornerRadius,
+ float borderThickness) {
+ float outerRoundBox = sdRoundedBox(p, size, cornerRadius);
+ float innerRoundBox = sdRoundedBox(p, size - vec2(borderThickness),
+ cornerRadius - borderThickness);
+ return subtract(outerRoundBox, innerRoundBox);
+ }
+ """
+
+ // Used non-trigonometry parametrization and Halley's method (iterative) for root finding.
+ // This is more expensive than the regular circle SDF, recommend to use the circle SDF if
+ // possible.
+ const val ELLIPSE_SDF = """float sdEllipse(vec2 p, vec2 wh) {
+ wh *= 0.5;
+
+ // symmetry
+ (wh.x > wh.y) ? wh = wh.yx, p = abs(p.yx) : p = abs(p);
+
+ vec2 u = wh*p, v = wh*wh;
+
+ float U1 = u.y/2.0; float U5 = 4.0*U1;
+ float U2 = v.y-v.x; float U6 = 6.0*U1;
+ float U3 = u.x-U2; float U7 = 3.0*U3;
+ float U4 = u.x+U2;
+
+ float t = 0.5;
+ for (int i = 0; i < 3; i ++) {
+ float F1 = t*(t*t*(U1*t+U3)+U4)-U1;
+ float F2 = t*t*(U5*t+U7)+U4;
+ float F3 = t*(U6*t+U7);
+
+ t += (F1*F2)/(F1*F3-F2*F2);
+ }
+
+ t = clamp(t, 0.0, 1.0);
+
+ float d = distance(p, wh*vec2(1.0-t*t,2.0*t)/(t*t+1.0));
+ d /= wh.y;
+
+ return (dot(p/wh,p/wh)>1.0) ? d : -d;
+ }
+
+ float ellipseRing(vec2 p, vec2 wh) {
+ vec2 thicknessHalf = wh * 0.25;
+
+ float outerEllipse = sdEllipse(p, wh + thicknessHalf);
+ float innerEllipse = sdEllipse(p, wh);
+
+ return subtract(outerEllipse, innerEllipse);
+ }
+ """
+
+ const val SHADER_SDF_OPERATION_LIB = """
+ float soften(float d, float blur) {
+ float blurHalf = blur * 0.5;
+ return smoothstep(-blurHalf, blurHalf, d);
+ }
+
+ float subtract(float outer, float inner) {
+ return max(outer, -inner);
+ }
+ """
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
new file mode 100644
index 0000000..beb54c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 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.screenshot
+
+import android.net.Uri
+import android.util.Log
+import android.view.WindowManager.ScreenshotType
+import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
+import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
+import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
+import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/**
+ * Processes a screenshot request sent from {@link ScreenshotHelper}.
+ */
+@SysUISingleton
+internal class RequestProcessor @Inject constructor(
+ private val controller: ScreenshotController,
+) {
+ fun processRequest(
+ @ScreenshotType type: Int,
+ onSavedListener: Consumer<Uri>,
+ request: ScreenshotRequest,
+ callback: RequestCallback
+ ) {
+
+ if (type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ val image = HardwareBitmapBundler.bundleToHardwareBitmap(request.bitmapBundle)
+
+ controller.handleImageAsScreenshot(
+ image, request.boundsInScreen, request.insets,
+ request.taskId, request.userId, request.topComponent, onSavedListener, callback
+ )
+ return
+ }
+
+ when (type) {
+ TAKE_SCREENSHOT_FULLSCREEN ->
+ controller.takeScreenshotFullscreen(null, onSavedListener, callback)
+ TAKE_SCREENSHOT_SELECTED_REGION ->
+ controller.takeScreenshotPartial(null, onSavedListener, callback)
+ else -> Log.w(TAG, "Invalid screenshot option: $type")
+ }
+ }
+
+ companion object {
+ const val TAG: String = "RequestProcessor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 32d8203..f1f0223 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -21,6 +21,7 @@
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE;
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI;
+import static com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
@@ -57,6 +58,8 @@
import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FlagListenable.FlagEvent;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -75,6 +78,8 @@
private final Handler mHandler;
private final Context mContext;
private final @Background Executor mBgExecutor;
+ private final RequestProcessor mProcessor;
+ private final FeatureFlags mFeatureFlags;
private final BroadcastReceiver mCloseSystemDialogs = new BroadcastReceiver() {
@Override
@@ -104,7 +109,8 @@
public TakeScreenshotService(ScreenshotController screenshotController, UserManager userManager,
DevicePolicyManager devicePolicyManager, UiEventLogger uiEventLogger,
ScreenshotNotificationsController notificationsController, Context context,
- @Background Executor bgExecutor) {
+ @Background Executor bgExecutor, FeatureFlags featureFlags,
+ RequestProcessor processor) {
if (DEBUG_SERVICE) {
Log.d(TAG, "new " + this);
}
@@ -116,6 +122,9 @@
mNotificationsController = notificationsController;
mContext = context;
mBgExecutor = bgExecutor;
+ mFeatureFlags = featureFlags;
+ mFeatureFlags.addListener(SCREENSHOT_REQUEST_PROCESSOR, FlagEvent::requestNoRestart);
+ mProcessor = processor;
}
@Override
@@ -218,6 +227,12 @@
mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()), 0,
topComponent == null ? "" : topComponent.getPackageName());
+ if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) {
+ Log.d(TAG, "handleMessage: Using request processor");
+ mProcessor.processRequest(msg.what, uriConsumer, screenshotRequest, requestCallback);
+ return true;
+ }
+
switch (msg.what) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
if (DEBUG_SERVICE) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 5793105..0f9ac36 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -264,14 +264,8 @@
Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
)
- carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
- listOf(
- header.context.getString(com.android.internal.R.string.status_bar_no_calling),
- header.context.getString(com.android.internal.R.string.status_bar_call_strength)
- )
- } else {
+ carrierIconSlots =
listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
- }
qsCarrierGroupController = qsCarrierGroupControllerBuilder
.setQSCarrierGroup(qsCarrierGroup)
.build()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 58e9bb8..4b8379a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -44,7 +44,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
-import android.app.ActivityManager;
import android.app.Fragment;
import android.app.StatusBarManager;
import android.content.ContentResolver;
@@ -288,12 +287,16 @@
private final NotificationPanelView mView;
private final VibratorHelper mVibratorHelper;
private final MetricsLogger mMetricsLogger;
- private final ActivityManager mActivityManager;
private final ConfigurationController mConfigurationController;
private final Provider<FlingAnimationUtils.Builder> mFlingAnimationUtilsBuilder;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final NotificationIconAreaController mNotificationIconAreaController;
+ // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
+ // changed.
+ private static final int CAP_HEIGHT = 1456;
+ private static final int FONT_HEIGHT = 2163;
+
/**
* Maximum time before which we will expand the panel even for slow motions when getting a
* touch passed over from launcher.
@@ -341,14 +344,14 @@
private final RecordingController mRecordingController;
private final PanelEventsEmitter mPanelEventsEmitter;
private boolean mSplitShadeEnabled;
- // The bottom padding reserved for elements of the keyguard measuring notifications
+ /** The bottom padding reserved for elements of the keyguard measuring notifications. */
private float mKeyguardNotificationBottomPadding;
/**
* The top padding from where notification should start in lockscreen.
* Should be static also during animations and should match the Y of the first notification.
*/
private float mKeyguardNotificationTopPadding;
- // Current max allowed keyguard notifications determined by measuring the panel
+ /** Current max allowed keyguard notifications determined by measuring the panel. */
private int mMaxAllowedKeyguardNotifications;
private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
@@ -728,7 +731,6 @@
AccessibilityManager accessibilityManager, @DisplayId int displayId,
KeyguardUpdateMonitor keyguardUpdateMonitor,
MetricsLogger metricsLogger,
- ActivityManager activityManager,
ConfigurationController configurationController,
Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
@@ -798,7 +800,6 @@
panelExpansionStateManager,
ambientState,
interactionJankMonitor,
- keyguardUnlockAnimationController,
systemClock);
mView = view;
mVibratorHelper = vibratorHelper;
@@ -808,7 +809,6 @@
mQRCodeScannerController = qrCodeScannerController;
mControlsComponent = controlsComponent;
mMetricsLogger = metricsLogger;
- mActivityManager = activityManager;
mConfigurationController = configurationController;
mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
mMediaHierarchyManager = mediaHierarchyManager;
@@ -1122,6 +1122,13 @@
}
}
+ /**
+ * Returns if there's a custom clock being presented.
+ */
+ public boolean hasCustomClock() {
+ return mKeyguardStatusViewController.hasCustomClock();
+ }
+
private void setCentralSurfaces(CentralSurfaces centralSurfaces) {
// TODO: this can be injected.
mCentralSurfaces = centralSurfaces;
@@ -1504,10 +1511,7 @@
}
private void updateKeyguardStatusViewAlignment(boolean animate) {
- boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
- .getVisibleNotificationCount() != 0
- || mMediaDataManager.hasActiveMediaOrRecommendation();
- boolean shouldBeCentered = !mSplitShadeEnabled || !hasVisibleNotifications || mDozing;
+ boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
if (mStatusViewCentered != shouldBeCentered) {
mStatusViewCentered = shouldBeCentered;
ConstraintSet constraintSet = new ConstraintSet();
@@ -1531,6 +1535,15 @@
mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(mStatusViewCentered));
}
+ private boolean shouldKeyguardStatusViewBeCentered() {
+ boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
+ .getVisibleNotificationCount() != 0
+ || mMediaDataManager.hasActiveMediaOrRecommendation();
+ boolean isOnAod = mDozing && mDozeParameters.getAlwaysOn();
+ return !mSplitShadeEnabled || !hasVisibleNotifications || isOnAod
+ || hasPulsingNotifications();
+ }
+
/**
* @return the padding of the stackscroller when unlocked
*/
@@ -3909,9 +3922,9 @@
public void onAnimationEnd(Animator animation) {
endAction.run();
}
- }).setUpdateListener(anim -> {
- mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
}).start();
+
+ mKeyguardStatusViewController.animateFoldToAod();
}
/**
@@ -3969,7 +3982,7 @@
* notification data being displayed. In the new notification pipeline, this is handled in
* {@link ShadeViewManager}.
*/
- public void updateNotificationViews(String reason) {
+ public void updateNotificationViews() {
mNotificationStackScrollLayoutController.updateFooter();
mNotificationIconAreaController.updateNotificationIcons(createVisibleEntriesList());
@@ -4426,7 +4439,7 @@
NotificationStackScrollLayout.OnEmptySpaceClickListener {
@Override
public void onEmptySpaceClicked(float x, float y) {
- onEmptySpaceClick(x);
+ onEmptySpaceClick();
}
}
@@ -4624,6 +4637,7 @@
public void onDozeAmountChanged(float linearAmount, float amount) {
mInterpolatedDarkAmount = amount;
mLinearDarkAmount = linearAmount;
+ mKeyguardStatusViewController.setDarkAmount(mInterpolatedDarkAmount);
mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
positionClockAndNotifications();
}
@@ -4733,8 +4747,11 @@
updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount());
setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
- // Update Clock Pivot (used by anti-burnin transformations)
- mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight());
+ // Update Clock Pivot
+ mKeyguardStatusViewController.setPivotX(((float) mView.getWidth()) / 2f);
+ mKeyguardStatusViewController.setPivotY(
+ (FONT_HEIGHT - CAP_HEIGHT) / 2048f
+ * mKeyguardStatusViewController.getClockTextSize());
// Calculate quick setting heights.
int oldMaxHeight = mQsMaxExpansionHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 15e1129..1d92105 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -89,7 +89,6 @@
Dumpable, ConfigurationListener {
private static final String TAG = "NotificationShadeWindowController";
- private static final boolean DEBUG = false;
private final Context mContext;
private final WindowManager mWindowManager;
@@ -190,7 +189,7 @@
return;
}
}
- mCallbacks.add(new WeakReference<StatusBarWindowCallback>(callback));
+ mCallbacks.add(new WeakReference<>(callback));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 121d69d..e52170e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -18,6 +18,8 @@
import static android.view.WindowInsets.Type.systemBars;
+import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
+
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.LayoutRes;
@@ -52,14 +54,12 @@
import com.android.internal.view.FloatingActionMode;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
/**
* Combined keyguard and notification panel view. Also holding backdrop and scrims.
*/
public class NotificationShadeWindowView extends FrameLayout {
public static final String TAG = "NotificationShadeWindowView";
- public static final boolean DEBUG = CentralSurfaces.DEBUG;
private int mRightInset = 0;
private int mLeftInset = 0;
@@ -221,7 +221,7 @@
}
}
- class LayoutParams extends FrameLayout.LayoutParams {
+ private static class LayoutParams extends FrameLayout.LayoutParams {
public boolean ignoreRightInset;
@@ -243,7 +243,7 @@
public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback,
int type) {
if (type == ActionMode.TYPE_FLOATING) {
- return startActionMode(originalView, callback, type);
+ return startActionMode(originalView, callback);
}
return super.startActionModeForChild(originalView, callback, type);
}
@@ -258,14 +258,10 @@
final FloatingActionMode mode =
new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar);
mFloatingActionModeOriginatingView = originatingView;
- mFloatingToolbarPreDrawListener =
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- mode.updateViewLocationInWindow();
- return true;
- }
- };
+ mFloatingToolbarPreDrawListener = () -> {
+ mode.updateViewLocationInWindow();
+ return true;
+ };
return mode;
}
@@ -292,10 +288,10 @@
}
private ActionMode startActionMode(
- View originatingView, ActionMode.Callback callback, int type) {
+ View originatingView, ActionMode.Callback callback) {
ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback);
- if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
+ if (wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
setHandledFloatingActionMode(mode);
} else {
mode = null;
@@ -382,7 +378,7 @@
/**
* Minimal window to satisfy FloatingToolbar.
*/
- private Window mFakeWindow = new Window(mContext) {
+ private final Window mFakeWindow = new Window(mContext) {
@Override
public void takeSurface(SurfaceHolder.Callback2 callback) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index 587e0e6d..02316b7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -48,13 +48,12 @@
private View mStackScroller;
private View mKeyguardStatusBar;
- private ArrayList<View> mDrawingOrderedChildren = new ArrayList<>();
- private ArrayList<View> mLayoutDrawingOrder = new ArrayList<>();
+ private final ArrayList<View> mDrawingOrderedChildren = new ArrayList<>();
+ private final ArrayList<View> mLayoutDrawingOrder = new ArrayList<>();
private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild);
private Consumer<WindowInsets> mInsetsChangedListener = insets -> {};
private Consumer<QS> mQSFragmentAttachedListener = qs -> {};
private QS mQs;
- private View mQSScrollView;
private View mQSContainer;
@Nullable
@@ -76,7 +75,6 @@
public void onFragmentViewCreated(String tag, Fragment fragment) {
mQs = (QS) fragment;
mQSFragmentAttachedListener.accept(mQs);
- mQSScrollView = mQs.getView().findViewById(R.id.expanded_qs_scroll_view);
mQSContainer = mQs.getView().findViewById(R.id.quick_settings_container);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelView.java b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
index 1082967..efff0db 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
@@ -49,10 +49,6 @@
super(context, attrs, defStyleAttr);
}
- public PanelView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
public void setOnTouchListener(PanelViewController.TouchHandler touchHandler) {
super.setOnTouchListener(touchHandler);
mTouchHandler = touchHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
index 229acf4..1a8a6d1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
@@ -19,11 +19,11 @@
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.classifier.Classifier.UNLOCK;
+import static com.android.systemui.shade.PanelView.DEBUG;
import static java.lang.Float.isNaN;
@@ -53,7 +53,6 @@
import com.android.systemui.animation.Interpolators;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.doze.DozeLog;
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
@@ -77,7 +76,6 @@
import java.util.List;
public abstract class PanelViewController {
- public static final boolean DEBUG = PanelView.DEBUG;
public static final String TAG = PanelView.class.getSimpleName();
public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
public static final float FLING_SPEED_UP_FACTOR = 0.6f;
@@ -97,7 +95,7 @@
protected boolean mTouchSlopExceededBeforeDown;
private float mMinExpandHeight;
private boolean mPanelUpdateWhenAnimatorEnds;
- private boolean mVibrateOnOpening;
+ private final boolean mVibrateOnOpening;
protected boolean mIsLaunchAnimationRunning;
private int mFixedDuration = NO_FIXED_DURATION;
protected float mOverExpansion;
@@ -144,7 +142,6 @@
private int mTouchSlop;
private float mSlopMultiplier;
protected boolean mHintAnimationRunning;
- private boolean mOverExpandedBeforeFling;
private boolean mTouchAboveFalsingThreshold;
private int mUnlockFalsingThreshold;
private boolean mTouchStartedInEmptyArea;
@@ -155,9 +152,9 @@
private ValueAnimator mHeightAnimator;
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
- private FlingAnimationUtils mFlingAnimationUtils;
- private FlingAnimationUtils mFlingAnimationUtilsClosing;
- private FlingAnimationUtils mFlingAnimationUtilsDismissing;
+ private final FlingAnimationUtils mFlingAnimationUtils;
+ private final FlingAnimationUtils mFlingAnimationUtilsClosing;
+ private final FlingAnimationUtils mFlingAnimationUtilsDismissing;
private final LatencyTracker mLatencyTracker;
private final FalsingManager mFalsingManager;
private final DozeLog mDozeLog;
@@ -178,9 +175,9 @@
/**
* Whether or not the PanelView can be expanded or collapsed with a drag.
*/
- private boolean mNotificationsDragEnabled;
+ private final boolean mNotificationsDragEnabled;
- private Interpolator mBounceInterpolator;
+ private final Interpolator mBounceInterpolator;
protected KeyguardBottomAreaView mKeyguardBottomArea;
/**
@@ -201,7 +198,6 @@
protected final AmbientState mAmbientState;
protected final LockscreenGestureLogger mLockscreenGestureLogger;
private final PanelExpansionStateManager mPanelExpansionStateManager;
- private final TouchHandler mTouchHandler;
private final InteractionJankMonitor mInteractionJankMonitor;
protected final SystemClock mSystemClock;
@@ -229,8 +225,6 @@
return mAmbientState;
}
- private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
-
public PanelViewController(
PanelView view,
FalsingManager falsingManager,
@@ -247,9 +241,7 @@
PanelExpansionStateManager panelExpansionStateManager,
AmbientState ambientState,
InteractionJankMonitor interactionJankMonitor,
- KeyguardUnlockAnimationController keyguardUnlockAnimationController,
SystemClock systemClock) {
- mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onKeyguardFadingAwayChanged() {
@@ -261,7 +253,7 @@
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mLockscreenGestureLogger = lockscreenGestureLogger;
mPanelExpansionStateManager = panelExpansionStateManager;
- mTouchHandler = createTouchHandler();
+ TouchHandler touchHandler = createTouchHandler();
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -274,7 +266,7 @@
});
mView.addOnLayoutChangeListener(createLayoutChangeListener());
- mView.setOnTouchListener(mTouchHandler);
+ mView.setOnTouchListener(touchHandler);
mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
mResources = mView.getResources();
@@ -398,7 +390,7 @@
public void startExpandMotion(float newX, float newY, boolean startTracking,
float expandedHeight) {
if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
- beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ beginJankMonitoring();
}
mInitialOffsetOnTouch = expandedHeight;
mInitialTouchY = newY;
@@ -475,7 +467,7 @@
} else if (!mCentralSurfaces.isBouncerShowing()
&& !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
&& !mKeyguardStateController.isKeyguardGoingAway()) {
- boolean expands = onEmptySpaceClick(mInitialTouchX);
+ boolean expands = onEmptySpaceClick();
onTrackingStopped(expands);
}
mVelocityTracker.clear();
@@ -670,7 +662,7 @@
@Override
public void onAnimationStart(Animator animation) {
if (!mStatusBarStateController.isDozing()) {
- beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ beginJankMonitoring();
}
}
@@ -702,10 +694,8 @@
mIsSpringBackAnimation = true;
ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0);
animator.addUpdateListener(
- animation -> {
- setOverExpansionInternal((float) animation.getAnimatedValue(),
- false /* isFromGesture */);
- });
+ animation -> setOverExpansionInternal((float) animation.getAnimatedValue(),
+ false /* isFromGesture */));
animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
animator.addListener(new AnimatorListenerAdapter() {
@@ -731,10 +721,10 @@
setAnimator(null);
mKeyguardStateController.notifyPanelFlingEnd();
if (!cancelled) {
- endJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ endJankMonitoring();
notifyExpandingFinished();
} else {
- cancelJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ cancelJankMonitoring();
}
updatePanelExpansionAndVisibility();
}
@@ -773,13 +763,6 @@
setExpandedHeight(currentMaxPanelHeight);
}
- private float getStackHeightFraction(float height) {
- final float gestureFraction = height / getMaxPanelHeight();
- final float stackHeightFraction = Interpolators.ACCELERATE_DECELERATE
- .getInterpolation(gestureFraction);
- return stackHeightFraction;
- }
-
public void setExpandedHeightInternal(float h) {
if (isNaN(h)) {
Log.wtf(TAG, "ExpandedHeight set to NaN");
@@ -911,13 +894,8 @@
return !isFullyCollapsed() && !mTracking && !mClosing;
}
- private final Runnable mFlingCollapseRunnable = new Runnable() {
- @Override
- public void run() {
- fling(0, false /* expand */, mNextCollapseSpeedUpFactor,
- false /* expandBecauseOfFalsing */);
- }
- };
+ private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
+ mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
public void expand(final boolean animate) {
if (!isFullyCollapsed() && !isCollapsing()) {
@@ -950,7 +928,7 @@
mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
if (mAnimateAfterExpanding) {
notifyExpandingStarted();
- beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ beginJankMonitoring();
fling(0, true /* expand */);
} else {
setExpandedFraction(1f);
@@ -1150,7 +1128,7 @@
*
* @return whether the panel will be expanded after the action performed by this method
*/
- protected boolean onEmptySpaceClick(float x) {
+ protected boolean onEmptySpaceClick() {
if (mHintAnimationRunning) {
return true;
}
@@ -1432,9 +1410,9 @@
// mHeightAnimator is null, there is no remaining frame, ends instrumenting.
if (mHeightAnimator == null) {
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- endJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ endJankMonitoring();
} else {
- cancelJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ cancelJankMonitoring();
}
}
break;
@@ -1465,28 +1443,32 @@
}
}
- private void beginJankMonitoring(int cuj) {
+ private void beginJankMonitoring() {
if (mInteractionJankMonitor == null) {
return;
}
InteractionJankMonitor.Configuration.Builder builder =
- InteractionJankMonitor.Configuration.Builder.withView(cuj, mView)
+ InteractionJankMonitor.Configuration.Builder.withView(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ mView)
.setTag(isFullyCollapsed() ? "Expand" : "Collapse");
mInteractionJankMonitor.begin(builder);
}
- private void endJankMonitoring(int cuj) {
+ private void endJankMonitoring() {
if (mInteractionJankMonitor == null) {
return;
}
- InteractionJankMonitor.getInstance().end(cuj);
+ InteractionJankMonitor.getInstance().end(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
}
- private void cancelJankMonitoring(int cuj) {
+ private void cancelJankMonitoring() {
if (mInteractionJankMonitor == null) {
return;
}
- InteractionJankMonitor.getInstance().cancel(cuj);
+ InteractionJankMonitor.getInstance().cancel(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
}
protected float getExpansionFraction() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 3013ad0..a57d849b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -61,21 +61,19 @@
private int mVisibleState = -1;
private DualToneHandler mDualToneHandler;
private boolean mForceHidden;
- private boolean mProviderModel;
/**
* Designated constructor
*/
public static StatusBarMobileView fromContext(
Context context,
- String slot,
- boolean providerModel
+ String slot
) {
LayoutInflater inflater = LayoutInflater.from(context);
StatusBarMobileView v = (StatusBarMobileView)
inflater.inflate(R.layout.status_bar_mobile_signal_group, null);
v.setSlot(slot);
- v.init(providerModel);
+ v.init();
v.setVisibleState(STATE_ICON);
return v;
}
@@ -108,17 +106,12 @@
outRect.bottom += translationY;
}
- private void init(boolean providerModel) {
- mProviderModel = providerModel;
+ private void init() {
mDualToneHandler = new DualToneHandler(getContext());
mMobileGroup = findViewById(R.id.mobile_group);
mMobile = findViewById(R.id.mobile_signal);
mMobileType = findViewById(R.id.mobile_type);
- if (mProviderModel) {
- mMobileRoaming = findViewById(R.id.mobile_roaming_large);
- } else {
- mMobileRoaming = findViewById(R.id.mobile_roaming);
- }
+ mMobileRoaming = findViewById(R.id.mobile_roaming);
mMobileRoamingSpace = findViewById(R.id.mobile_roaming_space);
mIn = findViewById(R.id.mobile_in);
mOut = findViewById(R.id.mobile_out);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java
index 6914ae6..1638780 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java
@@ -21,6 +21,7 @@
import android.telephony.SubscriptionInfo;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener;
@@ -36,6 +37,7 @@
* Implements network listeners and forwards the calls along onto other listeners but on
* the current or specified Looper.
*/
+@SysUISingleton
public class CallbackHandler extends Handler implements EmergencyListener, SignalCallback {
private static final String TAG = "CallbackHandler";
private static final int MSG_EMERGENCE_CHANGED = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
index 9d8667a..5cf1abc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
@@ -26,25 +26,17 @@
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings.Global;
-import android.telephony.AccessNetworkConstants;
import android.telephony.CellSignalStrength;
import android.telephony.CellSignalStrengthCdma;
-import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.telephony.ims.ImsException;
-import android.telephony.ims.ImsMmTelManager;
-import android.telephony.ims.ImsReasonInfo;
-import android.telephony.ims.ImsRegistrationAttributes;
-import android.telephony.ims.RegistrationManager.RegistrationCallback;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.settingslib.AccessibilityContentDescriptions;
import com.android.settingslib.SignalIcon.MobileIconGroup;
import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.mobile.MobileMappings.Config;
@@ -54,8 +46,6 @@
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.SignalStrengthUtil;
import com.android.systemui.R;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.util.CarrierConfigTracker;
import java.io.PrintWriter;
@@ -70,33 +60,22 @@
public class MobileSignalController extends SignalController<MobileState, MobileIconGroup> {
private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
private static final int STATUS_HISTORY_SIZE = 64;
- private static final int IMS_TYPE_WWAN = 1;
- private static final int IMS_TYPE_WLAN = 2;
- private static final int IMS_TYPE_WLAN_CROSS_SIM = 3;
private final TelephonyManager mPhone;
private final CarrierConfigTracker mCarrierConfigTracker;
- private final ImsMmTelManager mImsMmTelManager;
private final SubscriptionDefaults mDefaults;
private final String mNetworkNameDefault;
private final String mNetworkNameSeparator;
private final ContentObserver mObserver;
- private final boolean mProviderModelBehavior;
- private final Handler mReceiverHandler;
- private int mImsType = IMS_TYPE_WWAN;
// Save entire info for logging, we only use the id.
final SubscriptionInfo mSubscriptionInfo;
private Map<String, MobileIconGroup> mNetworkToIconLookup;
- private int mLastLevel;
private MobileIconGroup mDefaultIcons;
private Config mConfig;
@VisibleForTesting
boolean mInflateSignalStrengths = false;
- private int mLastWwanLevel;
- private int mLastWlanLevel;
- private int mLastWlanCrossSimLevel;
@VisibleForTesting
- MobileStatusTracker mMobileStatusTracker;
+ final MobileStatusTracker mMobileStatusTracker;
// Save the previous STATUS_HISTORY_SIZE states for logging.
private final String[] mMobileStatusHistory = new String[STATUS_HISTORY_SIZE];
@@ -133,52 +112,6 @@
}
};
- private final RegistrationCallback mRegistrationCallback = new RegistrationCallback() {
- @Override
- public void onRegistered(ImsRegistrationAttributes attributes) {
- Log.d(mTag, "onRegistered: " + "attributes=" + attributes);
- int imsTransportType = attributes.getTransportType();
- int registrationAttributes = attributes.getAttributeFlags();
- if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
- mImsType = IMS_TYPE_WWAN;
- IconState statusIcon = new IconState(
- true,
- getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
- getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
- notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
- } else if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
- if (registrationAttributes == 0) {
- mImsType = IMS_TYPE_WLAN;
- IconState statusIcon = new IconState(
- true,
- getCallStrengthIcon(mLastWlanLevel, /* isWifi= */true),
- getCallStrengthDescription(mLastWlanLevel, /* isWifi= */true));
- notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
- } else if (registrationAttributes
- == ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET) {
- mImsType = IMS_TYPE_WLAN_CROSS_SIM;
- IconState statusIcon = new IconState(
- true,
- getCallStrengthIcon(mLastWlanCrossSimLevel, /* isWifi= */false),
- getCallStrengthDescription(
- mLastWlanCrossSimLevel, /* isWifi= */false));
- notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
- }
- }
- }
-
- @Override
- public void onUnregistered(ImsReasonInfo info) {
- Log.d(mTag, "onDeregistered: " + "info=" + info);
- mImsType = IMS_TYPE_WWAN;
- IconState statusIcon = new IconState(
- true,
- getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
- getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
- notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
- }
- };
-
// TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
// need listener lists anymore.
public MobileSignalController(
@@ -192,7 +125,7 @@
SubscriptionDefaults defaults,
Looper receiverLooper,
CarrierConfigTracker carrierConfigTracker,
- FeatureFlags featureFlags
+ MobileStatusTrackerFactory mobileStatusTrackerFactory
) {
super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
NetworkCapabilities.TRANSPORT_CELLULAR, callbackHandler,
@@ -206,7 +139,6 @@
R.string.status_bar_network_name_separator).toString();
mNetworkNameDefault = getTextIfExists(
com.android.internal.R.string.lockscreen_carrier_default).toString();
- mReceiverHandler = new Handler(receiverLooper);
mNetworkToIconLookup = mapIconSets(mConfig);
mDefaultIcons = getDefaultIcons(mConfig);
@@ -223,10 +155,7 @@
updateTelephony();
}
};
- mImsMmTelManager = ImsMmTelManager.createForSubscriptionId(info.getSubscriptionId());
- mMobileStatusTracker = new MobileStatusTracker(mPhone, receiverLooper,
- info, mDefaults, mMobileCallback);
- mProviderModelBehavior = featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS);
+ mMobileStatusTracker = mobileStatusTrackerFactory.createTracker(mMobileCallback);
}
void setConfiguration(Config config) {
@@ -271,41 +200,14 @@
mContext.getContentResolver().registerContentObserver(Global.getUriFor(
Global.MOBILE_DATA + mSubscriptionInfo.getSubscriptionId()),
true, mObserver);
- if (mProviderModelBehavior) {
- mReceiverHandler.post(mTryRegisterIms);
- }
}
- // There is no listener to monitor whether the IMS service is ready, so we have to retry the
- // IMS registration.
- private final Runnable mTryRegisterIms = new Runnable() {
- private static final int MAX_RETRY = 12;
- private int mRetryCount;
-
- @Override
- public void run() {
- try {
- mRetryCount++;
- mImsMmTelManager.registerImsRegistrationCallback(
- mReceiverHandler::post, mRegistrationCallback);
- Log.d(mTag, "registerImsRegistrationCallback succeeded");
- } catch (RuntimeException | ImsException e) {
- if (mRetryCount < MAX_RETRY) {
- Log.e(mTag, mRetryCount + " registerImsRegistrationCallback failed", e);
- // Wait for 5 seconds to retry
- mReceiverHandler.postDelayed(mTryRegisterIms, 5000);
- }
- }
- }
- };
-
/**
* Stop listening for phone state changes.
*/
public void unregisterListener() {
mMobileStatusTracker.setListening(false);
mContext.getContentResolver().unregisterContentObserver(mObserver);
- mImsMmTelManager.unregisterImsRegistrationCallback(mRegistrationCallback);
}
private void updateInflateSignalStrength() {
@@ -394,7 +296,7 @@
CharSequence qsDescription = null;
if (mCurrentState.dataSim) {
- // If using provider model behavior, only show QS icons if the state is also default
+ // only show QS icons if the state is also default
if (!mCurrentState.isDefault) {
return new QsInfo(qsTypeIcon, qsIcon, qsDescription);
}
@@ -416,32 +318,15 @@
private SbInfo getSbInfo(String contentDescription, int dataTypeIcon) {
final boolean dataDisabled = mCurrentState.isDataDisabledOrNotDefault();
- boolean showTriangle = false;
- int typeIcon = 0;
- IconState statusIcon = null;
+ IconState statusIcon = new IconState(
+ mCurrentState.enabled && !mCurrentState.airplaneMode,
+ getCurrentIconId(), contentDescription);
- if (mProviderModelBehavior) {
- boolean showDataIconStatusBar = (mCurrentState.dataConnected || dataDisabled)
- && (mCurrentState.dataSim && mCurrentState.isDefault);
- typeIcon =
- (showDataIconStatusBar || mConfig.alwaysShowDataRatIcon) ? dataTypeIcon : 0;
- showDataIconStatusBar |= mCurrentState.roaming;
- statusIcon = new IconState(
- showDataIconStatusBar && !mCurrentState.airplaneMode,
- getCurrentIconId(), contentDescription);
-
- showTriangle = showDataIconStatusBar && !mCurrentState.airplaneMode;
- } else {
- statusIcon = new IconState(
- mCurrentState.enabled && !mCurrentState.airplaneMode,
- getCurrentIconId(), contentDescription);
-
- boolean showDataIconInStatusBar =
- (mCurrentState.dataConnected && mCurrentState.isDefault) || dataDisabled;
- typeIcon =
- (showDataIconInStatusBar || mConfig.alwaysShowDataRatIcon) ? dataTypeIcon : 0;
- showTriangle = mCurrentState.enabled && !mCurrentState.airplaneMode;
- }
+ boolean showDataIconInStatusBar =
+ (mCurrentState.dataConnected && mCurrentState.isDefault) || dataDisabled;
+ int typeIcon =
+ (showDataIconInStatusBar || mConfig.alwaysShowDataRatIcon) ? dataTypeIcon : 0;
+ boolean showTriangle = mCurrentState.enabled && !mCurrentState.airplaneMode;
return new SbInfo(showTriangle, typeIcon, statusIcon);
}
@@ -560,144 +445,7 @@
}
private void updateMobileStatus(MobileStatus mobileStatus) {
- int lastVoiceState = mCurrentState.getVoiceServiceState();
mCurrentState.setFromMobileStatus(mobileStatus);
-
- notifyMobileLevelChangeIfNecessary(mobileStatus.signalStrength);
- if (mProviderModelBehavior) {
- maybeNotifyCallStateChanged(lastVoiceState);
- }
- }
-
- /** Call state changed is only applicable when provider model behavior is true */
- private void maybeNotifyCallStateChanged(int lastVoiceState) {
- int currentVoiceState = mCurrentState.getVoiceServiceState();
- if (lastVoiceState == currentVoiceState) {
- return;
- }
- // Only update the no calling Status in the below scenarios
- // 1. The first valid voice state has been received
- // 2. The voice state has been changed and either the last or current state is
- // ServiceState.STATE_IN_SERVICE
- if (lastVoiceState == -1
- || (lastVoiceState == ServiceState.STATE_IN_SERVICE
- || currentVoiceState == ServiceState.STATE_IN_SERVICE)) {
- boolean isNoCalling = mCurrentState.isNoCalling();
- isNoCalling &= !hideNoCalling();
- IconState statusIcon = new IconState(isNoCalling,
- R.drawable.ic_qs_no_calling_sms,
- getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString());
- notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
- }
- }
-
- void updateNoCallingState() {
- int currentVoiceState = mCurrentState.getVoiceServiceState();
- boolean isNoCalling = currentVoiceState != ServiceState.STATE_IN_SERVICE;
- isNoCalling &= !hideNoCalling();
- IconState statusIcon = new IconState(isNoCalling,
- R.drawable.ic_qs_no_calling_sms,
- getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString());
- notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
- }
-
- private boolean hideNoCalling() {
- return mNetworkController.hasDefaultNetwork()
- && mCarrierConfigTracker.getNoCallingConfig(mSubscriptionInfo.getSubscriptionId());
- }
-
- private int getCallStrengthIcon(int level, boolean isWifi) {
- return isWifi ? TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[level]
- : TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[level];
- }
-
- private String getCallStrengthDescription(int level, boolean isWifi) {
- return isWifi
- ? getTextIfExists(AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH[level])
- .toString()
- : getTextIfExists(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[level])
- .toString();
- }
-
- void refreshCallIndicator(SignalCallback callback) {
- boolean isNoCalling = mCurrentState.isNoCalling();
- isNoCalling &= !hideNoCalling();
- IconState statusIcon = new IconState(isNoCalling,
- R.drawable.ic_qs_no_calling_sms,
- getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString());
- callback.setCallIndicator(statusIcon, mSubscriptionInfo.getSubscriptionId());
-
- switch (mImsType) {
- case IMS_TYPE_WWAN:
- statusIcon = new IconState(
- true,
- getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
- getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
- break;
- case IMS_TYPE_WLAN:
- statusIcon = new IconState(
- true,
- getCallStrengthIcon(mLastWlanLevel, /* isWifi= */true),
- getCallStrengthDescription(mLastWlanLevel, /* isWifi= */true));
- break;
- case IMS_TYPE_WLAN_CROSS_SIM:
- statusIcon = new IconState(
- true,
- getCallStrengthIcon(mLastWlanCrossSimLevel, /* isWifi= */false),
- getCallStrengthDescription(mLastWlanCrossSimLevel, /* isWifi= */false));
- }
- callback.setCallIndicator(statusIcon, mSubscriptionInfo.getSubscriptionId());
- }
-
- void notifyWifiLevelChange(int level) {
- if (!mProviderModelBehavior) {
- return;
- }
- mLastWlanLevel = level;
- if (mImsType != IMS_TYPE_WLAN) {
- return;
- }
- IconState statusIcon = new IconState(
- true,
- getCallStrengthIcon(level, /* isWifi= */true),
- getCallStrengthDescription(level, /* isWifi= */true));
- notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
- }
-
- void notifyDefaultMobileLevelChange(int level) {
- if (!mProviderModelBehavior) {
- return;
- }
- mLastWlanCrossSimLevel = level;
- if (mImsType != IMS_TYPE_WLAN_CROSS_SIM) {
- return;
- }
- IconState statusIcon = new IconState(
- true,
- getCallStrengthIcon(level, /* isWifi= */false),
- getCallStrengthDescription(level, /* isWifi= */false));
- notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
- }
-
- void notifyMobileLevelChangeIfNecessary(SignalStrength signalStrength) {
- if (!mProviderModelBehavior) {
- return;
- }
- int newLevel = getSignalLevel(signalStrength);
- if (newLevel != mLastLevel) {
- mLastLevel = newLevel;
- mLastWwanLevel = newLevel;
- if (mImsType == IMS_TYPE_WWAN) {
- IconState statusIcon = new IconState(
- true,
- getCallStrengthIcon(newLevel, /* isWifi= */false),
- getCallStrengthDescription(newLevel, /* isWifi= */false));
- notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
- }
- if (mCurrentState.dataSim) {
- mNetworkController.notifyDefaultMobileLevelChange(newLevel);
- }
- }
}
int getSignalLevel(SignalStrength signalStrength) {
@@ -801,19 +549,14 @@
mMobileStatusHistoryIndex = (mMobileStatusHistoryIndex + 1) % STATUS_HISTORY_SIZE;
}
- @VisibleForTesting
- void setImsType(int imsType) {
- mImsType = imsType;
- }
-
@Override
public void dump(PrintWriter pw) {
super.dump(pw);
pw.println(" mSubscription=" + mSubscriptionInfo + ",");
- pw.println(" mProviderModelBehavior=" + mProviderModelBehavior + ",");
pw.println(" mInflateSignalStrengths=" + mInflateSignalStrengths + ",");
pw.println(" isDataDisabled=" + isDataDisabled() + ",");
pw.println(" mNetworkToIconLookup=" + mNetworkToIconLookup + ",");
+ pw.println(" mMobileStatusTracker.isListening=" + mMobileStatusTracker.isListening());
pw.println(" MobileStatusHistory");
int size = 0;
for (int i = 0; i < STATUS_HISTORY_SIZE; i++) {
@@ -843,6 +586,11 @@
icon = iconState;
description = desc;
}
+
+ @Override
+ public String toString() {
+ return "QsInfo: ratTypeIcon=" + ratTypeIcon + " icon=" + icon;
+ }
}
/** Box for status bar icon info */
@@ -856,5 +604,11 @@
ratTypeIcon = typeIcon;
icon = iconState;
}
+
+ @Override
+ public String toString() {
+ return "SbInfo: showTriangle=" + showTriangle + " ratTypeIcon=" + ratTypeIcon
+ + " icon=" + icon;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
new file mode 100644
index 0000000..7938179
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.connectivity
+
+import android.content.Context
+import android.os.Looper
+import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyManager
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.MobileStatusTracker
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.CarrierConfigTracker
+import javax.inject.Inject
+
+/**
+ * Factory to make MobileSignalController injectable
+ */
+@SysUISingleton
+internal class MobileSignalControllerFactory @Inject constructor(
+ val context: Context,
+ val callbackHandler: CallbackHandler,
+ val carrierConfigTracker: CarrierConfigTracker,
+) {
+ fun createMobileSignalController(
+ config: MobileMappings.Config,
+ hasMobileData: Boolean,
+ phone: TelephonyManager,
+ networkController: NetworkControllerImpl,
+ subscriptionInfo: SubscriptionInfo,
+ subscriptionDefaults: MobileStatusTracker.SubscriptionDefaults,
+ receiverLooper: Looper,
+ ): MobileSignalController {
+ val mobileTrackerFactory = MobileStatusTrackerFactory(
+ phone,
+ receiverLooper,
+ subscriptionInfo,
+ subscriptionDefaults)
+
+ return MobileSignalController(
+ context,
+ config,
+ hasMobileData,
+ phone,
+ callbackHandler,
+ networkController,
+ subscriptionInfo,
+ subscriptionDefaults,
+ receiverLooper,
+ carrierConfigTracker,
+ mobileTrackerFactory,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileStatusTrackerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileStatusTrackerFactory.kt
new file mode 100644
index 0000000..a4c1a198
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileStatusTrackerFactory.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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.connectivity
+
+import android.os.Looper
+import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyManager
+import com.android.settingslib.mobile.MobileStatusTracker
+
+/**
+ * Factory for [MobileStatusTracker], which lives in SettingsLib
+ */
+class MobileStatusTrackerFactory (
+ val phone: TelephonyManager,
+ val receiverLooper: Looper,
+ val info: SubscriptionInfo,
+ val defaults: MobileStatusTracker.SubscriptionDefaults,
+) {
+ fun createTracker(
+ callback: MobileStatusTracker.Callback
+ ): MobileStatusTracker {
+ return MobileStatusTracker(
+ phone,
+ receiverLooper,
+ info,
+ defaults,
+ callback)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index a1dc7b4..b3dd853 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -71,8 +71,6 @@
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogLevel;
import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
@@ -134,12 +132,11 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final DemoModeController mDemoModeController;
private final Object mLock = new Object();
- private final boolean mProviderModelBehavior;
private Config mConfig;
private final CarrierConfigTracker mCarrierConfigTracker;
- private final FeatureFlags mFeatureFlags;
private final DumpManager mDumpManager;
private final LogBuffer mLogBuffer;
+ private final MobileSignalControllerFactory mMobileFactory;
private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -235,9 +232,9 @@
DemoModeController demoModeController,
CarrierConfigTracker carrierConfigTracker,
WifiStatusTrackerFactory trackerFactory,
+ MobileSignalControllerFactory mobileFactory,
@Main Handler handler,
InternetDialogFactory internetDialogFactory,
- FeatureFlags featureFlags,
DumpManager dumpManager,
@StatusBarNetworkControllerLog LogBuffer logBuffer) {
this(context, connectivityManager,
@@ -257,8 +254,8 @@
demoModeController,
carrierConfigTracker,
trackerFactory,
+ mobileFactory,
handler,
- featureFlags,
dumpManager,
logBuffer);
mReceiverHandler.post(mRegisterListeners);
@@ -283,8 +280,8 @@
DemoModeController demoModeController,
CarrierConfigTracker carrierConfigTracker,
WifiStatusTrackerFactory trackerFactory,
+ MobileSignalControllerFactory mobileFactory,
@Main Handler handler,
- FeatureFlags featureFlags,
DumpManager dumpManager,
LogBuffer logBuffer
) {
@@ -298,6 +295,7 @@
mCallbackHandler = callbackHandler;
mDataSaverController = new DataSaverControllerImpl(context);
mBroadcastDispatcher = broadcastDispatcher;
+ mMobileFactory = mobileFactory;
mSubscriptionManager = subManager;
mSubDefaults = defaultsHandler;
@@ -305,7 +303,6 @@
mHasMobileDataFeature = telephonyManager.isDataCapable();
mDemoModeController = demoModeController;
mCarrierConfigTracker = carrierConfigTracker;
- mFeatureFlags = featureFlags;
mDumpManager = dumpManager;
mLogBuffer = logBuffer;
@@ -457,7 +454,6 @@
};
mDemoModeController.addCallback(this);
- mProviderModelBehavior = mFeatureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS);
mDumpManager.registerDumpable(TAG, this);
}
@@ -498,16 +494,16 @@
// broadcasts
IntentFilter filter = new IntentFilter();
- filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
- filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
- filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
- filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
- filter.addAction(Intent.ACTION_SERVICE_STATE);
- filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
+ filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ filter.addAction(Intent.ACTION_SERVICE_STATE);
+ filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
filter.addAction(Settings.Panel.ACTION_INTERNET_CONNECTIVITY);
+ filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+ filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
+ filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
+ filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
mListening = true;
@@ -660,20 +656,6 @@
return controller != null ? controller.getNetworkNameForCarrierWiFi() : "";
}
- void notifyWifiLevelChange(int level) {
- for (int i = 0; i < mMobileSignalControllers.size(); i++) {
- MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
- mobileSignalController.notifyWifiLevelChange(level);
- }
- }
-
- void notifyDefaultMobileLevelChange(int level) {
- for (int i = 0; i < mMobileSignalControllers.size(); i++) {
- MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
- mobileSignalController.notifyDefaultMobileLevelChange(level);
- }
- }
-
private void notifyControllersMobileDataChanged() {
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
@@ -746,9 +728,6 @@
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
mobileSignalController.notifyListeners(cb);
- if (mProviderModelBehavior) {
- mobileSignalController.refreshCallIndicator(cb);
- }
}
mCallbackHandler.setListening(cb, true);
}
@@ -863,9 +842,6 @@
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
MobileSignalController controller = mMobileSignalControllers.valueAt(i);
controller.setConfiguration(mConfig);
- if (mProviderModelBehavior) {
- controller.refreshCallIndicator(mCallbackHandler);
- }
}
refreshLocale();
}
@@ -982,11 +958,15 @@
mMobileSignalControllers.put(subId, cachedControllers.get(subId));
cachedControllers.remove(subId);
} else {
- MobileSignalController controller = new MobileSignalController(mContext, mConfig,
- mHasMobileDataFeature, mPhone.createForSubscriptionId(subId),
- mCallbackHandler, this, subscriptions.get(i),
- mSubDefaults, mReceiverHandler.getLooper(), mCarrierConfigTracker,
- mFeatureFlags);
+ MobileSignalController controller = mMobileFactory.createMobileSignalController(
+ mConfig,
+ mHasMobileDataFeature,
+ mPhone.createForSubscriptionId(subId),
+ this,
+ subscriptions.get(i),
+ mSubDefaults,
+ mReceiverHandler.getLooper()
+ );
controller.setUserSetupComplete(mUserSetup);
mMobileSignalControllers.put(subId, controller);
if (subscriptions.get(i).getSimSlotIndex() == 0) {
@@ -1140,24 +1120,11 @@
|| mValidatedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
pushConnectivityToSignals();
- if (mProviderModelBehavior) {
- mNoDefaultNetwork = !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_CELLULAR)
- && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_WIFI)
- && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
- mCallbackHandler.setConnectivityStatus(mNoDefaultNetwork, !mInetCondition,
- mNoNetworksAvailable);
- for (int i = 0; i < mMobileSignalControllers.size(); i++) {
- MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
- mobileSignalController.updateNoCallingState();
- }
- notifyAllListeners();
- } else {
- mNoDefaultNetwork = !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_CELLULAR)
- && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_WIFI)
- && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
- mCallbackHandler.setConnectivityStatus(mNoDefaultNetwork, !mInetCondition,
- mNoNetworksAvailable);
- }
+ mNoDefaultNetwork = !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_WIFI)
+ && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
+ mCallbackHandler.setConnectivityStatus(mNoDefaultNetwork, !mInetCondition,
+ mNoNetworksAvailable);
}
/**
@@ -1347,7 +1314,7 @@
mMobileSignalControllers.clear();
int start = mSubscriptionManager.getActiveSubscriptionInfoCountMax();
for (int i = start /* get out of normal index range */; i < start + num; i++) {
- subs.add(addSignalController(i, i));
+ subs.add(addDemoModeSignalController(i, i));
}
mCallbackHandler.setSubs(subs);
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
@@ -1373,7 +1340,7 @@
List<SubscriptionInfo> subs = new ArrayList<>();
while (mMobileSignalControllers.size() <= slot) {
int nextSlot = mMobileSignalControllers.size();
- subs.add(addSignalController(nextSlot, nextSlot));
+ subs.add(addDemoModeSignalController(nextSlot, nextSlot));
}
if (!subs.isEmpty()) {
mCallbackHandler.setSubs(subs);
@@ -1463,14 +1430,20 @@
mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
}
- private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
+ private SubscriptionInfo addDemoModeSignalController(int id, int simSlotIndex) {
SubscriptionInfo info = new SubscriptionInfo(id, "", simSlotIndex, "", "", 0, 0, "", 0,
null, null, null, "", false, null, null);
- MobileSignalController controller = new MobileSignalController(mContext,
- mConfig, mHasMobileDataFeature,
- mPhone.createForSubscriptionId(info.getSubscriptionId()), mCallbackHandler, this,
- info, mSubDefaults, mReceiverHandler.getLooper(), mCarrierConfigTracker,
- mFeatureFlags);
+
+ MobileSignalController controller = mMobileFactory.createMobileSignalController(
+ mConfig,
+ mHasMobileDataFeature,
+ mPhone.createForSubscriptionId(info.getSubscriptionId()),
+ this,
+ info,
+ mSubDefaults,
+ mReceiverHandler.getLooper()
+ );
+
mMobileSignalControllers.put(id, controller);
controller.getState().userSetup = true;
return info;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
index 87cdb17..12f2c22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
@@ -222,7 +222,6 @@
mCurrentState.connected = mWifiTracker.connected;
mCurrentState.ssid = mWifiTracker.ssid;
mCurrentState.rssi = mWifiTracker.rssi;
- boolean levelChanged = mCurrentState.level != mWifiTracker.level;
mCurrentState.level = mWifiTracker.level;
mCurrentState.statusLabel = mWifiTracker.statusLabel;
mCurrentState.isCarrierMerged = mWifiTracker.isCarrierMerged;
@@ -230,10 +229,6 @@
mCurrentState.iconGroup =
mCurrentState.isCarrierMerged ? mCarrierMergedWifiIconGroup
: mUnmergedWifiIconGroup;
-
- if (levelChanged) {
- mNetworkController.notifyWifiLevelChange(mCurrentState.level);
- }
}
boolean isCarrierMergedWifi(int subId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 126a986..dbf4810 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -18,8 +18,10 @@
import android.animation.ObjectAnimator
import android.util.FloatProperty
+import com.android.systemui.Dumpable
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -32,17 +34,20 @@
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.min
@SysUISingleton
class NotificationWakeUpCoordinator @Inject constructor(
+ dumpManager: DumpManager,
private val mHeadsUpManager: HeadsUpManager,
private val statusBarStateController: StatusBarStateController,
private val bypassController: KeyguardBypassController,
private val dozeParameters: DozeParameters,
private val screenOffAnimationController: ScreenOffAnimationController
-) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener {
+) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener,
+ Dumpable {
private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>(
"notificationVisibility") {
@@ -60,6 +65,7 @@
private var mLinearDozeAmount: Float = 0.0f
private var mDozeAmount: Float = 0.0f
+ private var mDozeAmountSource: String = "init"
private var mNotificationVisibleAmount = 0.0f
private var mNotificationsVisible = false
private var mNotificationsVisibleForExpansion = false
@@ -142,6 +148,7 @@
}
init {
+ dumpManager.registerDumpable(this)
mHeadsUpManager.addListener(this)
statusBarStateController.addCallback(this)
addListener(object : WakeUpListener {
@@ -248,13 +255,14 @@
// Let's notify the scroller that an animation started
notifyAnimationStart(mLinearDozeAmount == 1.0f)
}
- setDozeAmount(linear, eased)
+ setDozeAmount(linear, eased, source = "StatusBar")
}
- fun setDozeAmount(linear: Float, eased: Float) {
+ fun setDozeAmount(linear: Float, eased: Float, source: String) {
val changed = linear != mLinearDozeAmount
mLinearDozeAmount = linear
mDozeAmount = eased
+ mDozeAmountSource = source
mStackScrollerController.setDozeAmount(mDozeAmount)
updateHideAmount()
if (changed && linear == 0.0f) {
@@ -271,7 +279,7 @@
// undefined state, so it's an indication that we should do state cleanup. We override
// the doze amount to 0f (not dozing) so that the notifications are no longer hidden.
// See: UnlockedScreenOffAnimationController.onFinishedWakingUp()
- setDozeAmount(0f, 0f)
+ setDozeAmount(0f, 0f, source = "Override: Shade->Shade (lock cancelled by unlock)")
}
if (overrideDozeAmountIfAnimatingScreenOff(mLinearDozeAmount)) {
@@ -311,12 +319,11 @@
*/
private fun overrideDozeAmountIfBypass(): Boolean {
if (bypassController.bypassEnabled) {
- var amount = 1.0f
- if (statusBarStateController.state == StatusBarState.SHADE ||
- statusBarStateController.state == StatusBarState.SHADE_LOCKED) {
- amount = 0.0f
+ if (statusBarStateController.state == StatusBarState.KEYGUARD) {
+ setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)")
+ } else {
+ setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
}
- setDozeAmount(amount, amount)
return true
}
return false
@@ -332,7 +339,7 @@
*/
private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean {
if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) {
- setDozeAmount(1f, 1f)
+ setDozeAmount(1f, 1f, source = "Override: animating screen off")
return true
}
@@ -426,4 +433,24 @@
*/
@JvmDefault fun onPulseExpansionChanged(expandingChanged: Boolean) {}
}
-}
\ No newline at end of file
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("mLinearDozeAmount: $mLinearDozeAmount")
+ pw.println("mDozeAmount: $mDozeAmount")
+ pw.println("mDozeAmountSource: $mDozeAmountSource")
+ pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount")
+ pw.println("mNotificationsVisible: $mNotificationsVisible")
+ pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion")
+ pw.println("mVisibilityAmount: $mVisibilityAmount")
+ pw.println("mLinearVisibilityAmount: $mLinearVisibilityAmount")
+ pw.println("pulseExpanding: $pulseExpanding")
+ pw.println("state: ${StatusBarState.toString(state)}")
+ pw.println("fullyAwake: $fullyAwake")
+ pw.println("wakingUp: $wakingUp")
+ pw.println("willWakeUp: $willWakeUp")
+ pw.println("collapsedEnoughToHide: $collapsedEnoughToHide")
+ pw.println("pulsing: $pulsing")
+ pw.println("notificationsFullyHidden: $notificationsFullyHidden")
+ pw.println("canShowPulsingHuns: $canShowPulsingHuns")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 6dbbf0d..fc8e7d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -30,7 +30,6 @@
import com.android.systemui.R;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.StatusBarIconView;
@@ -255,9 +254,7 @@
public void addMobileView(MobileIconState state) {
Log.d(TAG, "addMobileView: ");
- StatusBarMobileView view = StatusBarMobileView.fromContext(
- mContext, state.slot,
- mFeatureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS));
+ StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, state.slot);
view.applyMobileState(state);
view.setStaticDrawableColor(mColor);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index a2140c6ab..7b8c5fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -423,6 +423,21 @@
+ getActualPaddingEnd();
}
+ @VisibleForTesting
+ boolean shouldForceOverflow(int i, int speedBumpIndex, float iconAppearAmount,
+ int maxVisibleIcons) {
+ return speedBumpIndex != -1 && i >= speedBumpIndex
+ && iconAppearAmount > 0.0f || i >= maxVisibleIcons;
+ }
+
+ @VisibleForTesting
+ boolean isOverflowing(boolean isLastChild, float translationX, float layoutEnd,
+ float iconSize) {
+ // Layout end, as used here, does not include padding end.
+ final float overflowX = isLastChild ? layoutEnd : layoutEnd - iconSize;
+ return translationX >= overflowX;
+ }
+
/**
* Calculate the horizontal translations for each notification based on how much the icons
* are inserted into the notification container.
@@ -448,26 +463,26 @@
if (mFirstVisibleIconState == null) {
mFirstVisibleIconState = iconState;
}
- boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex
- && iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons;
- boolean isLastChild = i == childCount - 1;
- float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
- ? ((StatusBarIconView) view).getIconScaleIncreased()
- : 1f;
iconState.visibleState = iconState.hidden
? StatusBarIconView.STATE_HIDDEN
: StatusBarIconView.STATE_ICON;
- final float overflowDotX = layoutEnd - mIconSize;
- boolean isOverflowing = translationX > overflowDotX;
+ final boolean forceOverflow = shouldForceOverflow(i, mSpeedBumpIndex,
+ iconState.iconAppearAmount, maxVisibleIcons);
+ final boolean isOverflowing = forceOverflow || isOverflowing(
+ /* isLastChild= */ i == childCount - 1, translationX, layoutEnd, mIconSize);
- if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) {
- firstOverflowIndex = isLastChild && !forceOverflow ? i - 1 : i;
+ // First icon to overflow.
+ if (firstOverflowIndex == -1 && isOverflowing) {
+ firstOverflowIndex = i;
mVisualOverflowStart = layoutEnd - mIconSize;
if (forceOverflow || mIsStaticLayout) {
mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
}
}
+ final float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
+ ? ((StatusBarIconView) view).getIconScaleIncreased()
+ : 1f;
translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
}
mNumDots = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 31d9266..30b640b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -38,7 +38,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.StatusBarIconView;
@@ -361,9 +360,7 @@
}
private StatusBarMobileView onCreateStatusBarMobileView(String slot) {
- StatusBarMobileView view = StatusBarMobileView.fromContext(
- mContext, slot,
- mFeatureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS));
+ StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, slot);
return view;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index a7ab11c..26017b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -739,8 +739,10 @@
}
mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
- // setDozing(false) will call reset once we stop dozing.
- if (!mDozing) {
+ // setDozing(false) will call reset once we stop dozing. Also, if we're going away, there's
+ // no need to reset the keyguard views as we'll be gone shortly. Resetting now could cause
+ // unexpected visible behavior if the keyguard is still visible as we're animating unlocked.
+ if (!mDozing && !mKeyguardStateController.isKeyguardGoingAway()) {
// If Keyguard is reshown, don't hide the bouncer as it might just have been requested
// by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
reset(isOccluding /* hideBouncerWhenShowing*/);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index ee242a4..492734e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -26,8 +26,6 @@
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
import com.android.systemui.statusbar.connectivity.NetworkController;
@@ -66,7 +64,6 @@
private final Handler mHandler = Handler.getMain();
private final CarrierConfigTracker mCarrierConfigTracker;
private final TunerService mTunerService;
- private final FeatureFlags mFeatureFlags;
private boolean mHideAirplane;
private boolean mHideMobile;
@@ -90,8 +87,7 @@
CarrierConfigTracker carrierConfigTracker,
NetworkController networkController,
SecurityController securityController,
- TunerService tunerService,
- FeatureFlags featureFlags
+ TunerService tunerService
) {
mContext = context;
@@ -100,7 +96,6 @@
mNetworkController = networkController;
mSecurityController = securityController;
mTunerService = tunerService;
- mFeatureFlags = featureFlags;
mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane);
mSlotMobile = mContext.getString(com.android.internal.R.string.status_bar_mobile);
@@ -378,40 +373,6 @@
}
@Override
- public void setConnectivityStatus(boolean noDefaultNetwork, boolean noValidatedNetwork,
- boolean noNetworksAvailable) {
- if (!mFeatureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "setConnectivityStatus: "
- + "noDefaultNetwork = " + noDefaultNetwork + ","
- + "noValidatedNetwork = " + noValidatedNetwork + ","
- + "noNetworksAvailable = " + noNetworksAvailable);
- }
- WifiIconState newState = mWifiIconState.copy();
- newState.noDefaultNetwork = noDefaultNetwork;
- newState.noValidatedNetwork = noValidatedNetwork;
- newState.noNetworksAvailable = noNetworksAvailable;
- newState.slot = mSlotWifi;
- newState.airplaneSpacerVisible = mIsAirplaneMode;
- if (noDefaultNetwork && noNetworksAvailable && !mIsAirplaneMode) {
- newState.visible = true;
- newState.resId = R.drawable.ic_qs_no_internet_unavailable;
- } else if (noDefaultNetwork && !noNetworksAvailable
- && (!mIsAirplaneMode || (mIsAirplaneMode && mIsWifiEnabled))) {
- newState.visible = true;
- newState.resId = R.drawable.ic_qs_no_internet_available;
- } else {
- newState.visible = false;
- newState.resId = 0;
- }
- updateWifiIconWithState(newState);
- mWifiIconState = newState;
- }
-
-
- @Override
public void setEthernetIndicators(IconState state) {
boolean visible = state.visible && !mHideEthernet;
int resId = state.icon;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index b94f33a..29b4d5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -439,6 +439,7 @@
state |= DISABLE_CLOCK;
}
+
if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) {
if (mNetworkController.hasEmergencyCryptKeeperText()) {
state |= DISABLE_NOTIFICATION_ICONS;
@@ -448,6 +449,13 @@
}
}
+ // The shelf will be hidden when dozing with a custom clock, we must show notification
+ // icons in this occasion.
+ if (mStatusBarStateController.isDozing()
+ && mNotificationPanelViewController.hasCustomClock()) {
+ state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO;
+ }
+
if (mOngoingCallController.hasOngoingCall()) {
state &= ~DISABLE_ONGOING_CALL_CHIP;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt
new file mode 100644
index 0000000..6c02b0d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 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.pipeline
+
+import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * Interface exposing a flow for raw connectivity information. Clients should collect on
+ * [rawConnectivityInfoFlow] to get updates on connectivity information.
+ *
+ * Note: [rawConnectivityInfoFlow] should be a *hot* flow, so that we only create one instance of it
+ * and all clients get references to the same flow.
+ *
+ * This will be used for the new status bar pipeline to compile information we need to display some
+ * of the icons in the RHS of the status bar.
+ */
+interface ConnectivityInfoCollector {
+ val rawConnectivityInfoFlow: StateFlow<RawConnectivityInfo>
+}
+
+/**
+ * An object containing all of the raw connectivity information.
+ *
+ * Importantly, all the information in this object should not be processed at all (i.e., the data
+ * that we receive from callbacks should be piped straight into this object and not be filtered,
+ * manipulated, or processed in any way). Instead, any listeners on
+ * [ConnectivityInfoCollector.rawConnectivityInfoFlow] can do the processing.
+ *
+ * This allows us to keep all the processing in one place which is beneficial for logging and
+ * debugging purposes.
+ */
+data class RawConnectivityInfo(
+ val networkCapabilityInfo: Map<Int, NetworkCapabilityInfo> = emptyMap(),
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt
new file mode 100644
index 0000000..8d69422
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.pipeline
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilitiesRepo
+import kotlinx.coroutines.CoroutineScope
+import javax.inject.Inject
+import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * The real implementation of [ConnectivityInfoCollector] that will collect information from all the
+ * relevant connectivity callbacks and compile it into [rawConnectivityInfoFlow].
+ */
+@SysUISingleton
+class ConnectivityInfoCollectorImpl @Inject constructor(
+ networkCapabilitiesRepo: NetworkCapabilitiesRepo,
+ @Application scope: CoroutineScope,
+) : ConnectivityInfoCollector {
+ override val rawConnectivityInfoFlow: StateFlow<RawConnectivityInfo> =
+ // TODO(b/238425913): Collect all the separate flows for individual raw information into
+ // this final flow.
+ networkCapabilitiesRepo.dataStream
+ .map {
+ RawConnectivityInfo(networkCapabilityInfo = it)
+ }
+ .stateIn(scope, started = Lazily, initialValue = RawConnectivityInfo())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
index a841914..1aae250 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
@@ -19,7 +19,15 @@
import android.content.Context
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/**
* A processor that transforms raw connectivity information that we get from callbacks and turns it
@@ -27,20 +35,43 @@
*
* This will be used for the new status bar pipeline to calculate the list of icons that should be
* displayed in the RHS of the status bar.
+ *
+ * Anyone can listen to [processedInfoFlow] to get updates to the processed data.
*/
@SysUISingleton
class ConnectivityInfoProcessor @Inject constructor(
+ connectivityInfoCollector: ConnectivityInfoCollector,
context: Context,
- private val statusBarPipelineFlags: StatusBarPipelineFlags,
+ @Application private val scope: CoroutineScope,
+ statusBarPipelineFlags: StatusBarPipelineFlags,
) : CoreStartable(context) {
+ // Note: This flow will not start running until a client calls `collect` on it, which means that
+ // [connectivityInfoCollector]'s flow will also not start anything until that `collect` call
+ // happens.
+ val processedInfoFlow: Flow<ProcessedConnectivityInfo> =
+ if (!statusBarPipelineFlags.isNewPipelineEnabled())
+ emptyFlow()
+ else connectivityInfoCollector.rawConnectivityInfoFlow
+ .map { it.process() }
+ .stateIn(
+ scope,
+ started = Lazily,
+ initialValue = ProcessedConnectivityInfo()
+ )
+
override fun start() {
- if (statusBarPipelineFlags.isNewPipelineEnabled()) {
- init()
- }
}
- /** Initializes this processor and everything it depends on. */
- private fun init() {
- // TODO(b/238425913): Register all the connectivity callbacks here.
+ private fun RawConnectivityInfo.process(): ProcessedConnectivityInfo {
+ // TODO(b/238425913): Actually process the raw info into meaningful data.
+ return ProcessedConnectivityInfo(this.networkCapabilityInfo)
}
}
+
+/**
+ * An object containing connectivity info that has been processed into data that can be directly
+ * used by the status bar (and potentially other SysUI areas) to display icons.
+ */
+data class ProcessedConnectivityInfo(
+ val networkCapabilityInfo: Map<Int, NetworkCapabilityInfo> = emptyMap(),
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 771bb0c..c4e2b73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.pipeline.dagger
import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollector
+import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollectorImpl
import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor
import dagger.Binds
import dagger.Module
@@ -30,4 +32,9 @@
@IntoMap
@ClassKey(ConnectivityInfoProcessor::class)
abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable
+
+ @Binds
+ abstract fun provideConnectivityInfoCollector(
+ impl: ConnectivityInfoCollectorImpl
+ ): ConnectivityInfoCollector
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
deleted file mode 100644
index 4f39952..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * Copyright (C) 2022 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.content.BroadcastReceiver
-import android.testing.AndroidTestingRunner
-import android.widget.TextView
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.plugins.Clock
-import com.android.systemui.plugins.ClockAnimations
-import com.android.systemui.plugins.ClockEvents
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import java.util.TimeZone
-import org.junit.Assert.assertEquals
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyFloat
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.junit.MockitoJUnit
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class ClockEventControllerTest : SysuiTestCase() {
-
- @JvmField @Rule val mockito = MockitoJUnit.rule()
- @Mock private lateinit var statusBarStateController: StatusBarStateController
- @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock private lateinit var batteryController: BatteryController
- @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock private lateinit var configurationController: ConfigurationController
- @Mock private lateinit var animations: ClockAnimations
- @Mock private lateinit var events: ClockEvents
- @Mock private lateinit var clock: Clock
-
- private lateinit var clockEventController: ClockEventController
-
- @Before
- fun setUp() {
- whenever(clock.smallClock).thenReturn(TextView(context))
- whenever(clock.largeClock).thenReturn(TextView(context))
- whenever(clock.events).thenReturn(events)
- whenever(clock.animations).thenReturn(animations)
-
- clockEventController = ClockEventController(
- statusBarStateController,
- broadcastDispatcher,
- batteryController,
- keyguardUpdateMonitor,
- configurationController,
- context.resources,
- context
- )
- }
-
- @Test
- fun clockSet_validateInitialization() {
- clockEventController.clock = clock
-
- verify(clock).initialize(any(), anyFloat(), anyFloat())
- }
-
- @Test
- fun clockUnset_validateState() {
- clockEventController.clock = clock
- clockEventController.clock = null
-
- assertEquals(clockEventController.clock, null)
- }
-
- @Test
- fun themeChanged_verifyClockPaletteUpdated() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
- verify(configurationController).addCallback(capture(captor))
- captor.value.onThemeChanged()
-
- verify(events).onColorPaletteChanged(any())
- }
-
- @Test
- fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
- verify(batteryController).addCallback(capture(batteryCaptor))
- val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
- keyguardCaptor.value.onKeyguardVisibilityChanged(true)
- batteryCaptor.value.onBatteryLevelChanged(10, false, true)
-
- verify(animations).charge()
- }
-
- @Test
- fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
- verify(batteryController).addCallback(capture(batteryCaptor))
- val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
- keyguardCaptor.value.onKeyguardVisibilityChanged(true)
- batteryCaptor.value.onBatteryLevelChanged(10, false, true)
- batteryCaptor.value.onBatteryLevelChanged(10, false, true)
-
- verify(animations, times(1)).charge()
- }
-
- @Test
- fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
- verify(batteryController).addCallback(capture(batteryCaptor))
- val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
- keyguardCaptor.value.onKeyguardVisibilityChanged(false)
- batteryCaptor.value.onBatteryLevelChanged(10, false, true)
-
- verify(animations, never()).charge()
- }
-
- @Test
- fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
- verify(batteryController).addCallback(capture(batteryCaptor))
- val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
- keyguardCaptor.value.onKeyguardVisibilityChanged(true)
- batteryCaptor.value.onBatteryLevelChanged(10, false, false)
-
- verify(animations, never()).charge()
- }
-
- @Test
- fun localeCallback_verifyClockNotified() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val captor = argumentCaptor<BroadcastReceiver>()
- verify(broadcastDispatcher).registerReceiver(
- capture(captor), any(), eq(null), eq(null), anyInt(), eq(null)
- )
- captor.value.onReceive(context, mock())
-
- verify(events).onLocaleChanged(any())
- }
-
- @Test
- fun keyguardCallback_visibilityChanged_clockDozeCalled() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(captor))
-
- captor.value.onKeyguardVisibilityChanged(true)
- verify(animations, never()).doze(0f)
-
- captor.value.onKeyguardVisibilityChanged(false)
- verify(animations, times(1)).doze(0f)
- }
-
- @Test
- fun keyguardCallback_timeFormat_clockNotified() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(captor))
- captor.value.onTimeFormatChanged("12h")
-
- verify(events).onTimeFormatChanged(false)
- }
-
- @Test
- fun keyguardCallback_timezoneChanged_clockNotified() {
- val mockTimeZone = mock<TimeZone>()
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(captor))
- captor.value.onTimeZoneChanged(mockTimeZone)
-
- verify(events).onTimeZoneChanged(mockTimeZone)
- }
-
- @Test
- fun keyguardCallback_userSwitched_clockNotified() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(captor))
- captor.value.onUserSwitchComplete(10)
-
- verify(events).onTimeFormatChanged(false)
- }
-
- @Test
- fun keyguardCallback_verifyKeyguardChanged() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val captor = argumentCaptor<StatusBarStateController.StateListener>()
- verify(statusBarStateController).addCallback(capture(captor))
- captor.value.onDozeAmountChanged(0.4f, 0.6f)
-
- verify(animations).doze(0.4f)
- }
-
- @Test
- fun unregisterListeners_validate() {
- clockEventController.unregisterListeners()
- verify(broadcastDispatcher).unregisterReceiver(any())
- verify(configurationController).removeCallback(any())
- verify(batteryController).removeCallback(any())
- verify(keyguardUpdateMonitor).removeCallback(any())
- verify(statusBarStateController).removeCallback(any())
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 635ee9e..b2d9219 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -40,19 +41,24 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.colorextraction.ColorExtractor;
+import com.android.keyguard.clock.ClockManager;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.plugins.Clock;
+import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
-import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -74,12 +80,22 @@
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
- private ClockRegistry mClockRegistry;
+ private SysuiColorExtractor mColorExtractor;
+ @Mock
+ private ClockManager mClockManager;
@Mock
KeyguardSliceViewController mKeyguardSliceViewController;
@Mock
NotificationIconAreaController mNotificationIconAreaController;
@Mock
+ BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ BatteryController mBatteryController;
+ @Mock
+ KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ KeyguardBypassController mBypassController;
+ @Mock
LockscreenSmartspaceController mSmartspaceController;
@Mock
@@ -87,11 +103,11 @@
@Mock
KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock
- private Clock mClock;
+ private ClockPlugin mClockPlugin;
+ @Mock
+ ColorExtractor.GradientColors mGradientColors;
@Mock
DumpManager mDumpManager;
- @Mock
- ClockEventController mClockEventController;
@Mock
private NotificationIconContainer mNotificationIcons;
@@ -123,6 +139,8 @@
when(mView.getContext()).thenReturn(getContext());
when(mView.getResources()).thenReturn(mResources);
+ when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView);
+ when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView);
when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame);
when(mClockView.getContext()).thenReturn(getContext());
when(mLargeClockView.getContext()).thenReturn(getContext());
@@ -133,20 +151,23 @@
mController = new KeyguardClockSwitchController(
mView,
mStatusBarStateController,
- mClockRegistry,
+ mColorExtractor,
+ mClockManager,
mKeyguardSliceViewController,
mNotificationIconAreaController,
+ mBroadcastDispatcher,
+ mBatteryController,
+ mKeyguardUpdateMonitor,
mSmartspaceController,
mKeyguardUnlockAnimationController,
mSecureSettings,
mExecutor,
- mDumpManager,
- mClockEventController,
- mFeatureFlags
+ mResources,
+ mDumpManager
);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
- when(mClockRegistry.createCurrentClock()).thenReturn(mClock);
+ when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
mSliceView = new View(getContext());
when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
@@ -193,20 +214,20 @@
verifyAttachment(times(1));
listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
- verify(mClockEventController).unregisterListeners();
+ verify(mColorExtractor).removeOnColorsChangedListener(
+ any(ColorExtractor.OnColorsChangedListener.class));
}
@Test
public void testPluginPassesStatusBarState() {
- ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
+ ArgumentCaptor<ClockManager.ClockChangedListener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(ClockManager.ClockChangedListener.class);
mController.init();
- verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
+ verify(mClockManager).addOnClockChangedListener(listenerArgumentCaptor.capture());
- listenerArgumentCaptor.getValue().onClockChanged();
- verify(mView, times(2)).setClock(mClock, StatusBarState.SHADE);
- verify(mClockEventController, times(2)).setClock(mClock);
+ listenerArgumentCaptor.getValue().onClockChanged(mClockPlugin);
+ verify(mView).setClockPlugin(mClockPlugin, StatusBarState.SHADE);
}
@Test
@@ -263,8 +284,10 @@
}
private void verifyAttachment(VerificationMode times) {
- verify(mClockRegistry, times).registerClockChangeListener(
- any(ClockRegistry.ClockChangeListener.class));
- verify(mClockEventController, times).registerListeners();
+ verify(mClockManager, times).addOnClockChangedListener(
+ any(ClockManager.ClockChangedListener.class));
+ verify(mColorExtractor, times).addOnColorsChangedListener(
+ any(ColorExtractor.OnColorsChangedListener.class));
+ verify(mView, times).updateColors(mGradientColors);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index a0295d0..6c6f0ac 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
@@ -23,61 +24,56 @@
import static com.google.common.truth.Truth.assertThat;
-import static junit.framework.TestCase.assertEquals;
-
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Paint.Style;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.FrameLayout;
-import android.widget.TextView;
+import android.widget.TextClock;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.Clock;
+import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.StatusBarState;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
// Need to run on the main thread because KeyguardSliceView$Row init checks for
// the main thread before acquiring a wake lock. This class is constructed when
-// the keyguard_clock_switch layout is inflated.
+// the keyguard_clcok_switch layout is inflated.
@RunWithLooper(setAsMainLooper = true)
public class KeyguardClockSwitchTest extends SysuiTestCase {
- @Mock
- ViewGroup mMockKeyguardSliceView;
-
- @Mock
- Clock mClock;
-
- private FrameLayout mSmallClockFrame;
+ private FrameLayout mClockFrame;
private FrameLayout mLargeClockFrame;
+ private TextClock mBigClock;
+ private AnimatableClockView mClockView;
+ private AnimatableClockView mLargeClockView;
+ View mMockKeyguardSliceView;
KeyguardClockSwitch mKeyguardClockSwitch;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
+ mMockKeyguardSliceView = mock(KeyguardSliceView.class);
when(mMockKeyguardSliceView.getContext()).thenReturn(mContext);
when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area))
.thenReturn(mMockKeyguardSliceView);
- when(mClock.getSmallClock()).thenReturn(new TextView(getContext()));
- when(mClock.getLargeClock()).thenReturn(new TextView(getContext()));
-
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
@@ -97,68 +93,164 @@
});
mKeyguardClockSwitch =
(KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null);
- mSmallClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view);
+ mClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view);
+ mClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view);
mLargeClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view_large);
+ mLargeClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view_large);
+ mBigClock = new TextClock(getContext());
mKeyguardClockSwitch.mChildrenAreLaidOut = true;
+ MockitoAnnotations.initMocks(this);
}
@Test
- public void noPluginConnected_showNothing() {
- mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
- assertEquals(mLargeClockFrame.getChildCount(), 0);
- assertEquals(mSmallClockFrame.getChildCount(), 0);
+ public void onPluginConnected_showPluginClock() {
+ ClockPlugin plugin = mock(ClockPlugin.class);
+ TextClock pluginView = new TextClock(getContext());
+ when(plugin.getView()).thenReturn(pluginView);
+
+ mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
+
+ assertThat(mClockView.getVisibility()).isEqualTo(GONE);
+ assertThat(plugin.getView().getParent()).isEqualTo(mClockFrame);
}
@Test
- public void pluginConnectedThenDisconnected_showNothing() {
- mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
- assertEquals(mLargeClockFrame.getChildCount(), 1);
- assertEquals(mSmallClockFrame.getChildCount(), 1);
-
- mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
- assertEquals(mLargeClockFrame.getChildCount(), 0);
- assertEquals(mSmallClockFrame.getChildCount(), 0);
+ public void onPluginConnected_showPluginBigClock() {
+ // GIVEN the plugin returns a view for the big clock
+ ClockPlugin plugin = mock(ClockPlugin.class);
+ when(plugin.getBigClockView()).thenReturn(mBigClock);
+ // WHEN the plugin is connected
+ mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
+ // THEN the big clock container is visible and it is the parent of the
+ // big clock view.
+ assertThat(mLargeClockView.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mBigClock.getParent()).isEqualTo(mLargeClockFrame);
}
@Test
- public void onPluginConnected_showClock() {
- mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
-
- assertEquals(mClock.getSmallClock().getParent(), mSmallClockFrame);
- assertEquals(mClock.getLargeClock().getParent(), mLargeClockFrame);
+ public void onPluginConnected_nullView() {
+ ClockPlugin plugin = mock(ClockPlugin.class);
+ mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
+ assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
}
@Test
public void onPluginConnected_showSecondPluginClock() {
// GIVEN a plugin has already connected
- Clock otherClock = mock(Clock.class);
- when(otherClock.getSmallClock()).thenReturn(new TextView(getContext()));
- when(otherClock.getLargeClock()).thenReturn(new TextView(getContext()));
- mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
- mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
-
+ ClockPlugin plugin1 = mock(ClockPlugin.class);
+ when(plugin1.getView()).thenReturn(new TextClock(getContext()));
+ mKeyguardClockSwitch.setClockPlugin(plugin1, StatusBarState.KEYGUARD);
+ // WHEN a second plugin is connected
+ ClockPlugin plugin2 = mock(ClockPlugin.class);
+ when(plugin2.getView()).thenReturn(new TextClock(getContext()));
+ mKeyguardClockSwitch.setClockPlugin(plugin2, StatusBarState.KEYGUARD);
// THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
- assertThat(otherClock.getSmallClock().getParent()).isEqualTo(mSmallClockFrame);
- assertThat(otherClock.getLargeClock().getParent()).isEqualTo(mLargeClockFrame);
- assertThat(mClock.getSmallClock().getParent()).isNull();
- assertThat(mClock.getLargeClock().getParent()).isNull();
+ assertThat(plugin2.getView().getParent()).isEqualTo(mClockFrame);
+ assertThat(plugin1.getView().getParent()).isNull();
+ }
+
+ @Test
+ public void onPluginConnected_darkAmountInitialized() {
+ // GIVEN that the dark amount has already been set
+ mKeyguardClockSwitch.setDarkAmount(0.5f);
+ // WHEN a plugin is connected
+ ClockPlugin plugin = mock(ClockPlugin.class);
+ mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
+ // THEN dark amount should be initalized on the plugin.
+ verify(plugin).setDarkAmount(0.5f);
+ }
+
+ @Test
+ public void onPluginDisconnected_showDefaultClock() {
+ ClockPlugin plugin = mock(ClockPlugin.class);
+ TextClock pluginView = new TextClock(getContext());
+ when(plugin.getView()).thenReturn(pluginView);
+
+ mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
+ assertThat(mClockView.getVisibility()).isEqualTo(GONE);
+
+ mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
+ assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
+
+ assertThat(plugin.getView().getParent()).isNull();
+ }
+
+ @Test
+ public void onPluginDisconnected_hidePluginBigClock() {
+ // GIVEN the plugin returns a view for the big clock
+ ClockPlugin plugin = mock(ClockPlugin.class);
+ TextClock pluginView = new TextClock(getContext());
+ when(plugin.getBigClockView()).thenReturn(pluginView);
+ // WHEN the plugin is connected and then disconnected
+ mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
+ mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
+ // THEN the big lock container is GONE and the big clock view doesn't have
+ // a parent.
+ assertThat(mLargeClockView.getVisibility()).isEqualTo(VISIBLE);
+ assertThat(pluginView.getParent()).isNull();
+ }
+
+ @Test
+ public void onPluginDisconnected_nullView() {
+ ClockPlugin plugin = mock(ClockPlugin.class);
+ mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
+ mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
+ assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
}
@Test
public void onPluginDisconnected_secondOfTwoDisconnected() {
// GIVEN two plugins are connected
- Clock otherClock = mock(Clock.class);
- when(otherClock.getSmallClock()).thenReturn(new TextView(getContext()));
- when(otherClock.getLargeClock()).thenReturn(new TextView(getContext()));
- mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
- mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
+ ClockPlugin plugin1 = mock(ClockPlugin.class);
+ when(plugin1.getView()).thenReturn(new TextClock(getContext()));
+ mKeyguardClockSwitch.setClockPlugin(plugin1, StatusBarState.KEYGUARD);
+ ClockPlugin plugin2 = mock(ClockPlugin.class);
+ when(plugin2.getView()).thenReturn(new TextClock(getContext()));
+ mKeyguardClockSwitch.setClockPlugin(plugin2, StatusBarState.KEYGUARD);
// WHEN the second plugin is disconnected
- mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
- // THEN nothing should be shown
- assertThat(otherClock.getSmallClock().getParent()).isNull();
- assertThat(otherClock.getLargeClock().getParent()).isNull();
- assertThat(mClock.getSmallClock().getParent()).isNull();
- assertThat(mClock.getLargeClock().getParent()).isNull();
+ mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
+ // THEN the default clock should be shown.
+ assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
+ assertThat(plugin1.getView().getParent()).isNull();
+ assertThat(plugin2.getView().getParent()).isNull();
+ }
+
+ @Test
+ public void onPluginDisconnected_onDestroyView() {
+ // GIVEN a plugin is connected
+ ClockPlugin clockPlugin = mock(ClockPlugin.class);
+ when(clockPlugin.getView()).thenReturn(new TextClock(getContext()));
+ mKeyguardClockSwitch.setClockPlugin(clockPlugin, StatusBarState.KEYGUARD);
+ // WHEN the plugin is disconnected
+ mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
+ // THEN onDestroyView is called on the plugin
+ verify(clockPlugin).onDestroyView();
+ }
+
+ @Test
+ public void setTextColor_pluginClockSetTextColor() {
+ ClockPlugin plugin = mock(ClockPlugin.class);
+ TextClock pluginView = new TextClock(getContext());
+ when(plugin.getView()).thenReturn(pluginView);
+ mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
+
+ mKeyguardClockSwitch.setTextColor(Color.WHITE);
+
+ verify(plugin).setTextColor(Color.WHITE);
+ }
+
+
+ @Test
+ public void setStyle_pluginClockSetStyle() {
+ ClockPlugin plugin = mock(ClockPlugin.class);
+ TextClock pluginView = new TextClock(getContext());
+ when(plugin.getView()).thenReturn(pluginView);
+ Style style = mock(Style.class);
+ mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
+
+ mKeyguardClockSwitch.setStyle(style);
+
+ verify(plugin).setStyle(style);
}
@Test
@@ -170,7 +262,7 @@
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
- assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mClockFrame.getAlpha()).isEqualTo(0);
}
@Test
@@ -179,7 +271,7 @@
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
- assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mClockFrame.getAlpha()).isEqualTo(0);
}
@Test
@@ -189,8 +281,8 @@
mKeyguardClockSwitch.mClockInAnim.end();
mKeyguardClockSwitch.mClockOutAnim.end();
- assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1);
- assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE);
+ assertThat(mClockFrame.getAlpha()).isEqualTo(1);
+ assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
// only big clock is removed at switch
assertThat(mLargeClockFrame.getParent()).isNull();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
@@ -200,8 +292,8 @@
public void switchingToSmallClockNoAnimation_makesBigClockDisappear() {
mKeyguardClockSwitch.switchToClock(SMALL, false);
- assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1);
- assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE);
+ assertThat(mClockFrame.getAlpha()).isEqualTo(1);
+ assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
// only big clock is removed at switch
assertThat(mLargeClockFrame.getParent()).isNull();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 85ecfd3..035404c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -31,7 +32,6 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -41,6 +41,7 @@
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.IStrongAuthTracker;
import android.app.trust.TrustManager;
@@ -52,6 +53,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
@@ -68,6 +70,7 @@
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IRemoteCallback;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.telephony.ServiceState;
@@ -182,6 +185,18 @@
private ActiveUnlockConfig mActiveUnlockConfig;
@Mock
private KeyguardUpdateMonitorLogger mKeyguardUpdateMonitorLogger;
+ @Mock
+ private IActivityManager mActivityService;
+
+ private final int mCurrentUserId = 100;
+ private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
+
+ @Captor
+ private ArgumentCaptor<IBiometricEnabledOnKeyguardCallback>
+ mBiometricEnabledCallbackArgCaptor;
+ @Captor
+ private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
+
// Direct executor
private Executor mBackgroundExecutor = Runnable::run;
private Executor mMainExecutor = Runnable::run;
@@ -190,18 +205,16 @@
private TestableContext mSpiedContext;
private MockitoSession mMockitoSession;
private StatusBarStateController.StateListener mStatusBarStateListener;
+ private IBiometricEnabledOnKeyguardCallback mBiometricEnabledOnKeyguardCallback;
@Before
- public void setup() {
+ public void setup() throws RemoteException {
MockitoAnnotations.initMocks(this);
mSpiedContext = spy(mContext);
when(mPackageManager.hasSystemFeature(anyString())).thenReturn(true);
when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
- doAnswer(invocation -> {
- IBiometricEnabledOnKeyguardCallback callback = invocation.getArgument(0);
- callback.onChanged(true /* enabled */, KeyguardUpdateMonitor.getCurrentUser());
- return null;
- }).when(mBiometricManager).registerEnabledOnKeyguardCallback(any());
+ when(mActivityService.getCurrentUser()).thenReturn(mCurrentUserInfo);
+ when(mActivityService.getCurrentUserId()).thenReturn(mCurrentUserId);
when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
@@ -262,13 +275,20 @@
.startMocking();
ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
.when(SubscriptionManager::getDefaultSubscriptionId);
+ KeyguardUpdateMonitor.setCurrentUser(mCurrentUserId);
ExtendedMockito.doReturn(KeyguardUpdateMonitor.getCurrentUser())
.when(ActivityManager::getCurrentUser);
+ ExtendedMockito.doReturn(mActivityService).when(ActivityManager::getService);
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+ verify(mBiometricManager)
+ .registerEnabledOnKeyguardCallback(mBiometricEnabledCallbackArgCaptor.capture());
+ mBiometricEnabledOnKeyguardCallback = mBiometricEnabledCallbackArgCaptor.getValue();
+ biometricsEnabledForCurrentUser();
+
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
mStatusBarStateListener = mStatusBarStateListenerCaptor.getValue();
mKeyguardUpdateMonitor.registerCallback(mTestCallback);
@@ -718,7 +738,6 @@
verify(mLockPatternUtils).requireStrongAuth(anyInt(), anyInt());
}
-
@Test
public void testFaceAndFingerprintLockout() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
@@ -924,6 +943,7 @@
@Test
public void testSecondaryLockscreenRequirement() {
+ KeyguardUpdateMonitor.setCurrentUser(UserHandle.myUserId());
int user = KeyguardUpdateMonitor.getCurrentUser();
String packageName = "fake.test.package";
String cls = "FakeService";
@@ -1097,8 +1117,7 @@
@Test
public void testShouldNotUpdateBiometricListeningStateOnStatusBarStateChange() {
// GIVEN state for face auth should run aside from StatusBarState
- when(mDevicePolicyManager.getKeyguardDisabledFeatures(null,
- KeyguardUpdateMonitor.getCurrentUser())).thenReturn(0);
+ biometricsNotDisabledThroughDevicePolicyManager();
mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED);
setKeyguardBouncerVisibility(false /* isVisible */);
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
@@ -1153,6 +1172,306 @@
verify(mTestCallback).showTrustGrantedMessage("Unlocked by wearable");
}
+ @Test
+ public void testShouldListenForFace_whenFaceManagerNotAvailable_returnsFalse() {
+ mFaceManager = null;
+ mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenFpIsLockedOut_returnsFalse() throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ strongAuthNotRequired();
+ biometricsEnabledForCurrentUser();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ // Fingerprint is locked out.
+ fingerprintErrorLockedOut();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenFaceIsAlreadyAuthenticated_returnsFalse()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ bouncerFullyVisibleAndNotGoingToSleep();
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ strongAuthNotRequired();
+ biometricsEnabledForCurrentUser();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ userNotCurrentlySwitching();
+
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ triggerSuccessfulFaceAuth();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenUserIsNotPrimary_returnsFalse() throws RemoteException {
+ // This disables face auth
+ when(mUserManager.isPrimaryUser()).thenReturn(false);
+ mKeyguardUpdateMonitor =
+ new TestableKeyguardUpdateMonitor(mSpiedContext);
+
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ strongAuthNotRequired();
+ biometricsEnabledForCurrentUser();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenStrongAuthDoesNotAllowScanning_returnsFalse()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ biometricsEnabledForCurrentUser();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ userNotCurrentlySwitching();
+
+ // This disables face auth
+ when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
+ mTestableLooper.processAllMessages();
+
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenBiometricsDisabledForUser_returnsFalse()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ // This disables face auth
+ biometricsDisabledForCurrentUser();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenUserCurrentlySwitching_returnsFalse()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ userCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenSecureCameraLaunched_returnsFalse()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ secureCameraLaunched();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenOccludingAppRequestsFaceAuth_returnsTrue()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ secureCameraLaunched();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+
+ occludingAppRequestsFaceAuth();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenBouncerShowingAndDeviceIsAwake_returnsTrue()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+
+ bouncerFullyVisibleAndNotGoingToSleep();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenAuthInterruptIsActive_returnsTrue()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+
+ triggerAuthInterrupt();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+ }
+
+ private void triggerAuthInterrupt() {
+ mKeyguardUpdateMonitor.onAuthInterruptDetected(true);
+ }
+
+ private void occludingAppRequestsFaceAuth() {
+ mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
+ }
+
+ private void secureCameraLaunched() {
+ mKeyguardUpdateMonitor.onCameraLaunched();
+ }
+
+ private void userCurrentlySwitching() {
+ mKeyguardUpdateMonitor.setSwitchingUser(true);
+ }
+
+ private void fingerprintErrorLockedOut() {
+ mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
+ .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out");
+ }
+
+ private void triggerSuccessfulFaceAuth() {
+ mKeyguardUpdateMonitor.requestFaceAuth(true);
+ verify(mFaceManager).authenticate(any(),
+ any(),
+ mAuthenticationCallbackCaptor.capture(),
+ any(),
+ anyInt(),
+ anyBoolean());
+ mAuthenticationCallbackCaptor.getValue()
+ .onAuthenticationSucceeded(
+ new FaceManager.AuthenticationResult(null, null, mCurrentUserId, false));
+ }
+
+ private void currentUserIsPrimary() {
+ when(mUserManager.isPrimaryUser()).thenReturn(true);
+ }
+
+ private void biometricsNotDisabledThroughDevicePolicyManager() {
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null,
+ KeyguardUpdateMonitor.getCurrentUser())).thenReturn(0);
+ }
+
+ private void biometricsEnabledForCurrentUser() throws RemoteException {
+ mBiometricEnabledOnKeyguardCallback.onChanged(true, KeyguardUpdateMonitor.getCurrentUser());
+ }
+
+ private void biometricsDisabledForCurrentUser() throws RemoteException {
+ mBiometricEnabledOnKeyguardCallback.onChanged(
+ false,
+ KeyguardUpdateMonitor.getCurrentUser()
+ );
+ }
+
+ private void strongAuthNotRequired() {
+ when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(0);
+ }
+
+ private void currentUserDoesNotHaveTrust() {
+ mKeyguardUpdateMonitor.onTrustChanged(
+ false,
+ KeyguardUpdateMonitor.getCurrentUser(),
+ -1,
+ new ArrayList<>()
+ );
+ }
+
+ private void userNotCurrentlySwitching() {
+ mKeyguardUpdateMonitor.setSwitchingUser(false);
+ }
+
+ private void keyguardNotGoingAway() {
+ mKeyguardUpdateMonitor.setKeyguardGoingAway(false);
+ }
+
+ private void bouncerFullyVisibleAndNotGoingToSleep() {
+ mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true, true);
+ mKeyguardUpdateMonitor.dispatchFinishedGoingToSleep(/* value doesn't matter */1);
+ }
+
private void setKeyguardBouncerVisibility(boolean isVisible) {
mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible, isVisible);
mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index 6978490..3ac28c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -35,12 +35,12 @@
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.eq
-import org.mockito.Mockito.reset
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -63,6 +63,7 @@
controller = WiredChargingRippleController(
commandRegistry, batteryController, configurationController,
featureFlags, context, windowManager, systemClock, uiEventLogger)
+ rippleView.setupShader()
controller.rippleView = rippleView // Replace the real ripple view with a mock instance
controller.registerCallbacks()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index edcf479..8073103 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -39,6 +39,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.keyguard.KeyguardViewController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -112,7 +113,7 @@
mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager,
mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver,
mSystemActions, mOverviewProxyService, mAssistManagerLazy,
- () -> Optional.of(mock(CentralSurfaces.class)),
+ () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardViewController.class),
mNavigationModeController, mUserTracker, mDumpManager);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index fe5f433..51f0953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -72,6 +72,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardViewController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
@@ -193,6 +194,8 @@
@Mock
private CentralSurfaces mCentralSurfaces;
@Mock
+ private KeyguardViewController mKeyguardViewController;
+ @Mock
private UserContextProvider mUserContextProvider;
@Mock
private Resources mResources;
@@ -237,8 +240,8 @@
mock(AccessibilityButtonTargetsObserver.class),
mSystemActions, mOverviewProxyService,
() -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces),
- mock(NavigationModeController.class), mock(UserTracker.class),
- mock(DumpManager.class)));
+ mKeyguardViewController, mock(NavigationModeController.class),
+ mock(UserTracker.class), mock(DumpManager.class)));
mNavigationBar = createNavBar(mContext);
mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
});
@@ -377,7 +380,7 @@
// Verify navbar didn't alter and showing back icon when the keyguard is showing without
// requesting IME insets visible.
- doReturn(true).when(mCentralSurfaces).isKeyguardShowing();
+ doReturn(true).when(mKeyguardViewController).isShowing();
mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
BACK_DISPOSITION_DEFAULT, true);
assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 07c8af9..be14cc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -26,7 +26,6 @@
import com.android.systemui.colorextraction.SysuiColorExtractor
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
@@ -46,10 +45,10 @@
import org.mockito.Answers
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -162,7 +161,6 @@
@Test
fun testRSSISlot_notCombined() {
- `when`(featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)).thenReturn(false)
controller.init()
val captor = argumentCaptor<List<String>>()
@@ -174,20 +172,6 @@
}
@Test
- fun testRSSISlot_combined() {
- `when`(featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)).thenReturn(true)
- controller.init()
-
- val captor = argumentCaptor<List<String>>()
- verify(view).onAttach(any(), any(), capture(captor), any(), anyBoolean())
-
- assertThat(captor.value).containsExactly(
- mContext.getString(com.android.internal.R.string.status_bar_no_calling),
- mContext.getString(com.android.internal.R.string.status_bar_call_strength)
- )
- }
-
- @Test
fun testSingleCarrierCallback() {
controller.init()
reset(view)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index 09ce37b..1e7722a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -44,7 +44,6 @@
import androidx.test.filters.SmallTest;
import com.android.keyguard.CarrierTextManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
@@ -88,7 +87,6 @@
@Mock
private QSCarrier mQSCarrier3;
private TestableLooper mTestableLooper;
- @Mock private FeatureFlags mFeatureFlags;
@Mock
private QSCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener;
@@ -130,7 +128,7 @@
mQSCarrierGroupController = new QSCarrierGroupController.Builder(
mActivityStarter, handler, TestableLooper.get(this).getLooper(),
mNetworkController, mCarrierTextControllerBuilder, mContext, mCarrierConfigTracker,
- mFeatureFlags, mSlotIndexResolver)
+ mSlotIndexResolver)
.setQSCarrierGroup(mQSCarrierGroup)
.build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
index 5212255..99a17a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
@@ -22,13 +22,11 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.util.FeatureFlagUtils;
import android.view.LayoutInflater;
import android.view.View;
import androidx.test.filters.SmallTest;
-import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -59,14 +57,14 @@
@Test
public void testUpdateState_first() {
- CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false, false);
+ CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
assertTrue(mQSCarrier.updateState(c, false));
}
@Test
public void testUpdateState_same() {
- CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false, false);
+ CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
assertTrue(mQSCarrier.updateState(c, false));
assertFalse(mQSCarrier.updateState(c, false));
@@ -74,7 +72,7 @@
@Test
public void testUpdateState_changed() {
- CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false, false);
+ CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
assertTrue(mQSCarrier.updateState(c, false));
@@ -85,14 +83,14 @@
@Test
public void testUpdateState_singleCarrier_first() {
- CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false, false);
+ CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
assertTrue(mQSCarrier.updateState(c, true));
}
@Test
public void testUpdateState_singleCarrier_noShowIcon() {
- CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false, false);
+ CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
mQSCarrier.updateState(c, true);
@@ -101,7 +99,7 @@
@Test
public void testUpdateState_multiCarrier_showIcon() {
- CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false, false);
+ CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
mQSCarrier.updateState(c, false);
@@ -110,7 +108,7 @@
@Test
public void testUpdateState_changeSingleMultiSingle() {
- CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false, false);
+ CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
mQSCarrier.updateState(c, true);
assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
new file mode 100644
index 0000000..2d2f4cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 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.ripple
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RippleViewTest : SysuiTestCase() {
+ @Mock
+ private lateinit var rippleView: RippleView
+
+ @Before
+ fun setup() {
+ rippleView = RippleView(context, null)
+ }
+
+ @Test
+ fun testSetupShader_compilesCircle() {
+ rippleView.setupShader(RippleShader.RippleShape.CIRCLE)
+ }
+
+ @Test
+ fun testSetupShader_compilesRoundedBox() {
+ rippleView.setupShader(RippleShader.RippleShape.ROUNDED_BOX)
+ }
+
+ @Test
+ fun testSetupShader_compilesEllipse() {
+ rippleView.setupShader(RippleShader.RippleShape.ELLIPSE)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
new file mode 100644
index 0000000..002f23a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 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.screenshot
+
+import android.content.ComponentName
+import android.graphics.Bitmap
+import android.graphics.ColorSpace
+import android.graphics.Insets
+import android.graphics.Rect
+import android.hardware.HardwareBuffer
+import android.net.Uri
+import android.view.WindowManager
+import android.view.WindowManager.ScreenshotSource
+import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
+import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import org.junit.Test
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.isNull
+
+class RequestProcessorTest {
+ private val controller = mock<ScreenshotController>()
+ private val bitmapCaptor = argumentCaptor<Bitmap>()
+
+ @Test
+ fun testFullScreenshot() {
+ val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_KEY_CHORD)
+ val onSavedListener = mock<Consumer<Uri>>()
+ val callback = mock<RequestCallback>()
+ val processor = RequestProcessor(controller)
+
+ processor.processRequest(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, onSavedListener,
+ request, callback)
+
+ verify(controller).takeScreenshotFullscreen(/* topComponent */ isNull(),
+ eq(onSavedListener), eq(callback))
+ }
+
+ @Test
+ fun testSelectedRegionScreenshot() {
+ val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_KEY_CHORD)
+ val onSavedListener = mock<Consumer<Uri>>()
+ val callback = mock<RequestCallback>()
+ val processor = RequestProcessor(controller)
+
+ processor.processRequest(WindowManager.TAKE_SCREENSHOT_SELECTED_REGION, onSavedListener,
+ request, callback)
+
+ verify(controller).takeScreenshotPartial(/* topComponent */ isNull(),
+ eq(onSavedListener), eq(callback))
+ }
+
+ @Test
+ fun testProvidedImageScreenshot() {
+ val taskId = 1111
+ val userId = 2222
+ val bounds = Rect(50, 50, 150, 150)
+ val topComponent = ComponentName("test", "test")
+ val processor = RequestProcessor(controller)
+
+ val buffer = HardwareBuffer.create(100, 100, HardwareBuffer.RGBA_8888, 1,
+ HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
+ val bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
+ val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
+
+ val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_OTHER, bitmapBundle,
+ bounds, Insets.NONE, taskId, userId, topComponent)
+
+ val onSavedListener = mock<Consumer<Uri>>()
+ val callback = mock<RequestCallback>()
+
+ processor.processRequest(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, onSavedListener,
+ request, callback)
+
+ verify(controller).handleImageAsScreenshot(
+ bitmapCaptor.capture(), eq(bounds), eq(Insets.NONE), eq(taskId), eq(userId),
+ eq(topComponent), eq(onSavedListener), eq(callback)
+ )
+
+ assertThat(bitmapCaptor.value.equalsHardwareBitmap(bitmap)).isTrue()
+ }
+
+ private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean {
+ return bitmap.hardwareBuffer == this.hardwareBuffer &&
+ bitmap.colorSpace == this.colorSpace
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index c9405c8..fc28349 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -47,6 +47,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.graphics.PointF;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
@@ -461,6 +462,7 @@
NotificationWakeUpCoordinator coordinator =
new NotificationWakeUpCoordinator(
+ mDumpManager,
mock(HeadsUpManagerPhone.class),
new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager,
mInteractionJankMonitor),
@@ -527,7 +529,7 @@
mNotificationShadeWindowController,
mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
- mMetricsLogger, mActivityManager, mConfigurationController,
+ mMetricsLogger, mConfigurationController,
() -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
mConversationNotificationManager, mMediaHiearchyManager,
mStatusBarKeyguardViewManager,
@@ -902,6 +904,76 @@
}
@Test
+ public void keyguardStatusView_splitShade_dozing_alwaysDozingOn_isCentered() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ setDozing(/* dozing= */ true, /* dozingAlwaysOn= */ true);
+
+ assertThat(isKeyguardStatusViewCentered()).isTrue();
+ }
+
+ @Test
+ public void keyguardStatusView_splitShade_dozing_alwaysDozingOff_isNotCentered() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ setDozing(/* dozing= */ true, /* dozingAlwaysOn= */ false);
+
+ assertThat(isKeyguardStatusViewCentered()).isFalse();
+ }
+
+ @Test
+ public void keyguardStatusView_splitShade_notDozing_alwaysDozingOn_isNotCentered() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ true);
+
+ assertThat(isKeyguardStatusViewCentered()).isFalse();
+ }
+
+ @Test
+ public void keyguardStatusView_splitShade_pulsing_isCentered() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(true);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ true);
+
+ assertThat(isKeyguardStatusViewCentered()).isFalse();
+ }
+
+ @Test
+ public void keyguardStatusView_splitShade_notPulsing_isNotCentered() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(false);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ true);
+
+ assertThat(isKeyguardStatusViewCentered()).isFalse();
+ }
+
+ @Test
+ public void keyguardStatusView_singleShade_isCentered() {
+ enableSplitShade(/* enabled= */ false);
+ // The conditions below would make the clock NOT be centered on split shade.
+ // On single shade it should always be centered though.
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(false);
+ mStatusBarStateController.setState(KEYGUARD);
+ setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
+
+ assertThat(isKeyguardStatusViewCentered()).isFalse();
+ }
+
+ @Test
public void testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy() {
givenViewAttached();
when(mResources.getBoolean(
@@ -1473,4 +1545,19 @@
.thenReturn(splitShadeFullTransitionDistance);
mNotificationPanelViewController.updateResources();
}
+
+ private void setDozing(boolean dozing, boolean dozingAlwaysOn) {
+ when(mDozeParameters.getAlwaysOn()).thenReturn(dozingAlwaysOn);
+ mNotificationPanelViewController.setDozing(
+ /* dozing= */ dozing,
+ /* animate= */ false,
+ /* wakeUpTouchLocation= */ new PointF()
+ );
+ }
+
+ private boolean isKeyguardStatusViewCentered() {
+ mNotificationPanelViewController.updateResources();
+ return getConstraintSetLayout(R.id.keyguard_status_view).endToEnd
+ == ConstraintSet.PARENT_ID;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 131eac6..1cbb8d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -130,16 +130,13 @@
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
val list = registry.getClocks()
- assertEquals(
- list,
- listOf(
- ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
- ClockMetadata("clock_1", "clock 1"),
- ClockMetadata("clock_2", "clock 2"),
- ClockMetadata("clock_3", "clock 3"),
- ClockMetadata("clock_4", "clock 4")
- )
- )
+ assertEquals(list, listOf(
+ ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+ ClockMetadata("clock_1", "clock 1"),
+ ClockMetadata("clock_2", "clock 2"),
+ ClockMetadata("clock_3", "clock 3"),
+ ClockMetadata("clock_4", "clock 4")
+ ))
}
@Test
@@ -161,14 +158,11 @@
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
val list = registry.getClocks()
- assertEquals(
- list,
- listOf(
- ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
- ClockMetadata("clock_1", "clock 1"),
- ClockMetadata("clock_2", "clock 2")
- )
- )
+ assertEquals(list, listOf(
+ ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+ ClockMetadata("clock_1", "clock 1"),
+ ClockMetadata("clock_2", "clock 2")
+ ))
assertEquals(registry.createExampleClock("clock_1"), mockClock)
assertEquals(registry.createExampleClock("clock_2"), mockClock)
@@ -228,7 +222,7 @@
pluginListener.onPluginConnected(plugin2, mockContext)
var changeCallCount = 0
- registry.registerClockChangeListener { changeCallCount++ }
+ registry.registerClockChangeListener({ changeCallCount++ })
pluginListener.onPluginDisconnected(plugin1)
assertEquals(0, changeCallCount)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 0d1879c..f8a0d2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -70,8 +70,6 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -127,8 +125,8 @@
protected CarrierConfigTracker mCarrierConfigTracker;
protected FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
protected Handler mMainHandler;
- protected FeatureFlags mFeatureFlags;
protected WifiStatusTrackerFactory mWifiStatusTrackerFactory;
+ protected MobileSignalControllerFactory mMobileFactory;
protected int mSubId;
@@ -158,9 +156,6 @@
@Before
public void setUp() throws Exception {
- mFeatureFlags = mock(FeatureFlags.class);
- when(mFeatureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)).thenReturn(false);
-
mInstrumentation = InstrumentationRegistry.getInstrumentation();
Settings.Global.putInt(mContext.getContentResolver(), Global.AIRPLANE_MODE_ON, 0);
TestableResources res = mContext.getOrCreateTestableResources();
@@ -224,6 +219,11 @@
mWifiStatusTrackerFactory = new WifiStatusTrackerFactory(
mContext, mMockWm, mMockNsm, mMockCm, mMainHandler);
+ mMobileFactory = new MobileSignalControllerFactory(
+ mContext,
+ mCallbackHandler,
+ mCarrierConfigTracker
+ );
mNetworkController = new NetworkControllerImpl(mContext,
mMockCm,
@@ -243,8 +243,8 @@
mDemoModeController,
mCarrierConfigTracker,
mWifiStatusTrackerFactory,
+ mMobileFactory,
mMainHandler,
- mFeatureFlags,
mock(DumpManager.class),
mock(LogBuffer.class)
);
@@ -438,10 +438,6 @@
updateSignalStrength();
}
- public void setImsType(int imsType) {
- mMobileSignalController.setImsType(imsType);
- }
-
public void setIsGsm(boolean gsm) {
when(mSignalStrength.isGsm()).thenReturn(gsm);
updateSignalStrength();
@@ -637,5 +633,4 @@
protected void assertDataNetworkNameEquals(String expected) {
assertEquals("Data network name", expected, mNetworkController.getMobileDataNetworkName());
}
-
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index e3dd6f4..ed8a3e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -145,8 +145,8 @@
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
+ mMobileFactory,
new Handler(TestableLooper.get(this).getLooper()),
- mFeatureFlags,
mock(DumpManager.class),
mock(LogBuffer.class));
setupNetworkController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index 698899a..a76676e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -85,8 +85,8 @@
mDemoModeController,
mCarrierConfigTracker,
mWifiStatusTrackerFactory,
+ mMobileFactory,
mMainHandler,
- mFeatureFlags,
mock(DumpManager.class),
mock(LogBuffer.class)
);
@@ -121,8 +121,8 @@
mDemoModeController,
mCarrierConfigTracker,
mWifiStatusTrackerFactory,
+ mMobileFactory,
mMainHandler,
- mFeatureFlags,
mock(DumpManager.class),
mock(LogBuffer.class));
TestableLooper.get(this).processAllMessages();
@@ -155,8 +155,8 @@
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
+ mMobileFactory,
mMainHandler,
- mFeatureFlags,
mock(DumpManager.class),
mock(LogBuffer.class));
setupNetworkController();
@@ -192,8 +192,8 @@
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
+ mMobileFactory,
mMainHandler,
- mFeatureFlags,
mock(DumpManager.class),
mock(LogBuffer.class));
mNetworkController.registerListeners();
@@ -277,8 +277,8 @@
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
+ mMobileFactory,
mMainHandler,
- mFeatureFlags,
mock(DumpManager.class),
mock(LogBuffer.class));
setupNetworkController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index 3f71491..68170ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -30,7 +30,6 @@
import android.net.vcn.VcnTransportInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.telephony.CellSignalStrength;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -285,44 +284,6 @@
verifyLastMobileDataIndicatorsForVcn(false, 1, 0, false);
}
- @Test
- public void testCallStrengh() {
- if (true) return;
- String testSsid = "Test SSID";
- setWifiEnabled(true);
- setWifiState(true, testSsid);
- // Set the ImsType to be IMS_TYPE_WLAN
- setImsType(2);
- setWifiLevel(1);
- for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
- setWifiLevel(testLevel);
- verifyLastCallStrength(TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[testLevel]);
- }
- // Set the ImsType to be IMS_TYPE_WWAN
- setImsType(1);
- setupDefaultSignal();
- for (int testStrength = 0;
- testStrength < CellSignalStrength.getNumSignalStrengthLevels(); testStrength++) {
- setLevel(testStrength);
- verifyLastCallStrength(TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[testStrength]);
- }
- }
-
- @Test
- public void testNonPrimaryWiFi() {
- if (true) return;
- String testSsid = "Test SSID";
- setWifiEnabled(true);
- setWifiState(true, testSsid);
- // Set the ImsType to be IMS_TYPE_WLAN
- setImsType(2);
- setWifiLevel(1);
- verifyLastCallStrength(TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[1]);
- when(mWifiInfo.isPrimary()).thenReturn(false);
- setWifiLevel(3);
- verifyLastCallStrength(TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[1]);
- }
-
protected void setWifiActivity(int activity) {
// TODO: Not this, because this variable probably isn't sticking around.
mNetworkController.mWifiSignalController.setActivity(activity);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 2ff6dd4..086e5df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -153,6 +153,106 @@
assertTrue(iconContainer.hasOverflow())
}
+ @Test
+ fun shouldForceOverflow_appearingAboveSpeedBump_true() {
+ val forceOverflow = iconContainer.shouldForceOverflow(
+ /* i= */ 1,
+ /* speedBumpIndex= */ 0,
+ /* iconAppearAmount= */ 1f,
+ /* maxVisibleIcons= */ 5
+ )
+ assertTrue(forceOverflow);
+ }
+
+ @Test
+ fun shouldForceOverflow_moreThanMaxVisible_true() {
+ val forceOverflow = iconContainer.shouldForceOverflow(
+ /* i= */ 10,
+ /* speedBumpIndex= */ 11,
+ /* iconAppearAmount= */ 0f,
+ /* maxVisibleIcons= */ 5
+ )
+ assertTrue(forceOverflow);
+ }
+
+ @Test
+ fun shouldForceOverflow_belowSpeedBumpAndLessThanMaxVisible_false() {
+ val forceOverflow = iconContainer.shouldForceOverflow(
+ /* i= */ 0,
+ /* speedBumpIndex= */ 11,
+ /* iconAppearAmount= */ 0f,
+ /* maxVisibleIcons= */ 5
+ )
+ assertFalse(forceOverflow);
+ }
+
+ @Test
+ fun isOverflowing_lastChildXLessThanLayoutEnd_false() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ true,
+ /* translationX= */ 0f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertFalse(isOverflowing)
+ }
+
+
+ @Test
+ fun isOverflowing_lastChildXEqualToLayoutEnd_true() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ true,
+ /* translationX= */ 10f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertTrue(isOverflowing)
+ }
+
+ @Test
+ fun isOverflowing_lastChildXGreaterThanLayoutEnd_true() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ true,
+ /* translationX= */ 20f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertTrue(isOverflowing)
+ }
+
+ @Test
+ fun isOverflowing_notLastChildXLessThanDotX_false() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ false,
+ /* translationX= */ 0f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertFalse(isOverflowing)
+ }
+
+ @Test
+ fun isOverflowing_notLastChildXGreaterThanDotX_true() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ false,
+ /* translationX= */ 20f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertTrue(isOverflowing)
+ }
+
+ @Test
+ fun isOverflowing_notLastChildXEqualToDotX_true() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ false,
+ /* translationX= */ 8f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertTrue(isOverflowing)
+ }
+
private fun mockStatusBarIcon() : StatusBarIconView {
val iconView = mock(StatusBarIconView::class.java)
whenever(iconView.width).thenReturn(10)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index f7a4314..52a573f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -329,9 +329,10 @@
}
@Test
- public void disable_isDozing_clockAndSystemInfoVisible() {
+ public void disable_isDozingButNoCustomClock_clockAndSystemInfoVisible() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(true);
+ when(mNotificationPanelViewController.hasCustomClock()).thenReturn(false);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -340,9 +341,10 @@
}
@Test
- public void disable_NotDozing_clockAndSystemInfoVisible() {
+ public void disable_customClockButNotDozing_clockAndSystemInfoVisible() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -351,6 +353,40 @@
}
@Test
+ public void disable_dozingAndCustomClock_clockAndSystemInfoHidden() {
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+ when(mStatusBarStateController.isDozing()).thenReturn(true);
+ when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
+
+ // Make sure they start out as visible
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertEquals(View.VISIBLE, getClockView().getVisibility());
+
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
+ assertEquals(View.GONE, getClockView().getVisibility());
+ }
+
+ @Test
+ public void onDozingChanged_clockAndSystemInfoVisibilitiesUpdated() {
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+ when(mStatusBarStateController.isDozing()).thenReturn(true);
+ when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
+
+ // Make sure they start out as visible
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertEquals(View.VISIBLE, getClockView().getVisibility());
+
+ fragment.onDozingChanged(true);
+
+ // When this callback is triggered, we want to make sure the clock and system info
+ // visibilities are recalculated. Since dozing=true, they shouldn't be visible.
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
+ assertEquals(View.GONE, getClockView().getVisibility());
+ }
+
+ @Test
public void disable_headsUpShouldBeVisibleTrue_clockDisabled() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt
new file mode 100644
index 0000000..515a7c9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 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.pipeline
+
+import android.net.NetworkCapabilities
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when` as whenever
+
+@OptIn(InternalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ConnectivityInfoProcessorTest : SysuiTestCase() {
+
+ private val statusBarPipelineFlags = mock<StatusBarPipelineFlags>()
+
+ @Before
+ fun setUp() {
+ whenever(statusBarPipelineFlags.isNewPipelineEnabled()).thenReturn(true)
+ }
+
+ @Test
+ fun collectorInfoUpdated_processedInfoAlsoUpdated() = runBlocking {
+ // GIVEN a processor hooked up to a collector
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val collector = FakeConnectivityInfoCollector()
+ val processor = ConnectivityInfoProcessor(
+ collector,
+ context,
+ scope,
+ statusBarPipelineFlags,
+ )
+
+ var mostRecentValue: ProcessedConnectivityInfo? = null
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ processor.processedInfoFlow.collect {
+ mostRecentValue = it
+ }
+ }
+
+ // WHEN the collector emits a value
+ val networkCapabilityInfo = mapOf(
+ 10 to NetworkCapabilityInfo(mock(), NetworkCapabilities.Builder().build())
+ )
+ collector.emitValue(RawConnectivityInfo(networkCapabilityInfo))
+ // Because our job uses [CoroutineStart.UNDISPATCHED], it executes in the same thread as
+ // this test. So, our test needs to yield to let the job run.
+ // Note: Once we upgrade our Kotlin coroutines testing library, we won't need this.
+ yield()
+
+ // THEN the processor receives it
+ assertThat(mostRecentValue?.networkCapabilityInfo).isEqualTo(networkCapabilityInfo)
+
+ job.cancel()
+ scope.cancel()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt
new file mode 100644
index 0000000..710e5f6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.pipeline
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * A test-friendly implementation of [ConnectivityInfoCollector] that just emits whatever value it
+ * receives in [emitValue].
+ */
+class FakeConnectivityInfoCollector : ConnectivityInfoCollector {
+ private val _rawConnectivityInfoFlow = MutableStateFlow(RawConnectivityInfo())
+ override val rawConnectivityInfoFlow = _rawConnectivityInfoFlow.asStateFlow()
+
+ suspend fun emitValue(value: RawConnectivityInfo) {
+ _rawConnectivityInfoFlow.emit(value)
+ }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index a6e73e4..bed69b2 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -368,6 +368,7 @@
private static final int MSG_DISPATCH_DEVICE_VOLUME_BEHAVIOR = 47;
private static final int MSG_ROTATION_UPDATE = 48;
private static final int MSG_FOLD_UPDATE = 49;
+ private static final int MSG_RESET_SPATIALIZER = 50;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -375,6 +376,7 @@
private static final int MSG_DISABLE_AUDIO_FOR_UID = 100;
private static final int MSG_INIT_STREAMS_VOLUMES = 101;
private static final int MSG_INIT_SPATIALIZER = 102;
+
// end of messages handled under wakelock
// retry delay in case of failure to indicate system ready to AudioFlinger
@@ -8297,6 +8299,10 @@
onPersistSpatialAudioDeviceSettings();
break;
+ case MSG_RESET_SPATIALIZER:
+ mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect);
+ break;
+
case MSG_CHECK_MUSIC_ACTIVE:
onCheckMusicActive((String) msg.obj);
break;
@@ -9275,6 +9281,16 @@
/*arg1*/ 0, /*arg2*/ 0, TAG, /*delay*/ 0);
}
+ /**
+ * post a message to schedule a reset of the spatializer state
+ */
+ void postResetSpatializer() {
+ sendMsg(mAudioHandler,
+ MSG_RESET_SPATIALIZER,
+ SENDMSG_REPLACE,
+ /*arg1*/ 0, /*arg2*/ 0, TAG, /*delay*/ 0);
+ }
+
void onInitSpatializer() {
final String settings = mSettings.getSecureStringForUser(mContentResolver,
Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT);
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 1def72b..e27fb11 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -389,10 +389,10 @@
try {
mSpat.setLevel(level);
} catch (RemoteException e) {
- Log.e(TAG, "Can't set spatializer level", e);
- mState = STATE_NOT_SUPPORTED;
- mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
- enabled = false;
+ Log.e(TAG, "onRoutingUpdated() Can't set spatializer level", e);
+ // try to recover by resetting the native spatializer state
+ postReset();
+ return;
}
}
@@ -404,6 +404,10 @@
}
}
+ private void postReset() {
+ mAudioService.postResetSpatializer();
+ }
+
//------------------------------------------------------
// spatializer callback from native
private final class SpatializerCallback extends INativeSpatializerCallback.Stub {
@@ -751,33 +755,29 @@
if (enabled) {
throw (new IllegalStateException("Can't enable when uninitialized"));
}
- return;
+ break;
case STATE_NOT_SUPPORTED:
if (enabled) {
Log.e(TAG, "Can't enable when unsupported");
}
- return;
+ break;
case STATE_DISABLED_UNAVAILABLE:
case STATE_DISABLED_AVAILABLE:
if (enabled) {
createSpat();
onRoutingUpdated();
- break;
- } else {
- // already in disabled state
- return;
- }
+ // onRoutingUpdated() can update the "enabled" state based on context
+ // and will call setDispatchFeatureEnabledState().
+ } // else { nothing to do as already disabled }
+ break;
case STATE_ENABLED_UNAVAILABLE:
case STATE_ENABLED_AVAILABLE:
if (!enabled) {
releaseSpat();
- break;
- } else {
- // already in enabled state
- return;
- }
+ setDispatchFeatureEnabledState(false, "setSpatializerEnabledInt");
+ } // else { nothing to do as already enabled }
+ break;
}
- setDispatchFeatureEnabledState(enabled, "setSpatializerEnabledInt");
}
synchronized int getCapableImmersiveAudioLevel() {
@@ -1161,8 +1161,11 @@
case STATE_DISABLED_AVAILABLE:
case STATE_ENABLED_AVAILABLE:
if (mSpat == null) {
- throw (new IllegalStateException(
- "null Spatializer when calling " + funcName));
+ // try to recover by resetting the native spatializer state
+ Log.e(TAG, "checkSpatForHeadTracking(): "
+ + "native spatializer should not be null in state: " + mState);
+ postReset();
+ return false;
}
break;
}
@@ -1252,8 +1255,8 @@
case STATE_DISABLED_AVAILABLE:
case STATE_ENABLED_AVAILABLE:
if (mSpat == null) {
- throw (new IllegalStateException(
- "null Spatializer for setParameter for key:" + key));
+ Log.e(TAG, "setParameter(" + key + "): null spatializer in state: " + mState);
+ return;
}
break;
}
@@ -1276,8 +1279,8 @@
case STATE_DISABLED_AVAILABLE:
case STATE_ENABLED_AVAILABLE:
if (mSpat == null) {
- throw (new IllegalStateException(
- "null Spatializer for getParameter for key:" + key));
+ Log.e(TAG, "getParameter(" + key + "): null spatializer in state: " + mState);
+ return;
}
break;
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 86b8d32..af15735 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -498,6 +498,29 @@
return IKEV2_VPN_RETRY_DELAYS_SEC[retryCount];
}
}
+
+ /** Get single threaded executor for IKEv2 VPN */
+ public ScheduledThreadPoolExecutor newScheduledThreadPoolExecutor() {
+ return new ScheduledThreadPoolExecutor(1);
+ }
+
+ /** Get a NetworkAgent instance */
+ public NetworkAgent newNetworkAgent(
+ @NonNull Context context,
+ @NonNull Looper looper,
+ @NonNull String logTag,
+ @NonNull NetworkCapabilities nc,
+ @NonNull LinkProperties lp,
+ @NonNull NetworkScore score,
+ @NonNull NetworkAgentConfig config,
+ @Nullable NetworkProvider provider) {
+ return new NetworkAgent(context, looper, logTag, nc, lp, score, config, provider) {
+ @Override
+ public void onNetworkUnwanted() {
+ // We are user controlled, not driven by NetworkRequest.
+ }
+ };
+ }
}
public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd,
@@ -1474,15 +1497,10 @@
? Arrays.asList(mConfig.underlyingNetworks) : null);
mNetworkCapabilities = capsBuilder.build();
- mNetworkAgent = new NetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
+ mNetworkAgent = mDeps.newNetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
mNetworkCapabilities, lp,
new NetworkScore.Builder().setLegacyInt(VPN_DEFAULT_SCORE).build(),
- networkAgentConfig, mNetworkProvider) {
- @Override
- public void onNetworkUnwanted() {
- // We are user controlled, not driven by NetworkRequest.
- }
- };
+ networkAgentConfig, mNetworkProvider);
final long token = Binder.clearCallingIdentity();
try {
mNetworkAgent.register();
@@ -2692,11 +2710,10 @@
* of the mutable Ikev2VpnRunner fields. The Ikev2VpnRunner is built mostly lock-free by
* virtue of everything being serialized on this executor.
*/
- @NonNull
- private final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(1);
+ @NonNull private final ScheduledThreadPoolExecutor mExecutor;
- @Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostTimeout;
- @Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionTimeout;
+ @Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostFuture;
+ @Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionFuture;
/** Signal to ensure shutdown is honored even if a new Network is connected. */
private boolean mIsRunning = true;
@@ -2714,7 +2731,7 @@
@Nullable private LinkProperties mUnderlyingLinkProperties;
private final String mSessionKey;
- @Nullable private IkeSession mSession;
+ @Nullable private IkeSessionWrapper mSession;
@Nullable private IkeSessionConnectionInfo mIkeConnectionInfo;
// mMobikeEnabled can only be updated after IKE AUTH is finished.
@@ -2728,9 +2745,11 @@
*/
private int mRetryCount = 0;
- IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
+ IkeV2VpnRunner(
+ @NonNull Ikev2VpnProfile profile, @NonNull ScheduledThreadPoolExecutor executor) {
super(TAG);
mProfile = profile;
+ mExecutor = executor;
mIpSecManager = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
mNetworkCallback = new VpnIkev2Utils.Ikev2VpnNetworkCallback(TAG, this, mExecutor);
mSessionKey = UUID.randomUUID().toString();
@@ -2743,7 +2762,7 @@
// To avoid hitting RejectedExecutionException upon shutdown of the mExecutor */
mExecutor.setRejectedExecutionHandler(
- (r, executor) -> {
+ (r, exe) -> {
Log.d(TAG, "Runnable " + r + " rejected by the mExecutor");
});
}
@@ -2884,7 +2903,6 @@
mConfig.dnsServers.addAll(dnsAddrStrings);
mConfig.underlyingNetworks = new Network[] {network};
-
mConfig.disallowedApplications = getAppExclusionList(mPackage);
networkAgent = mNetworkAgent;
@@ -2900,6 +2918,10 @@
} else {
// Underlying networks also set in agentConnect()
networkAgent.setUnderlyingNetworks(Collections.singletonList(network));
+ mNetworkCapabilities =
+ new NetworkCapabilities.Builder(mNetworkCapabilities)
+ .setUnderlyingNetworks(Collections.singletonList(network))
+ .build();
}
lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
@@ -2933,6 +2955,8 @@
}
try {
+ mTunnelIface.setUnderlyingNetwork(mIkeConnectionInfo.getNetwork());
+
// Transforms do not need to be persisted; the IkeSession will keep
// them alive for us
mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
@@ -3114,13 +3138,13 @@
// If the default network is lost during the retry delay, the mActiveNetwork will be
// null, and the new IKE session won't be established until there is a new default
// network bringing up.
- mScheduledHandleRetryIkeSessionTimeout =
+ mScheduledHandleRetryIkeSessionFuture =
mExecutor.schedule(() -> {
startOrMigrateIkeSession(mActiveNetwork);
- // Reset mScheduledHandleRetryIkeSessionTimeout since it's already run on
+ // Reset mScheduledHandleRetryIkeSessionFuture since it's already run on
// executor thread.
- mScheduledHandleRetryIkeSessionTimeout = null;
+ mScheduledHandleRetryIkeSessionFuture = null;
}, retryDelay, TimeUnit.SECONDS);
}
@@ -3163,12 +3187,10 @@
mActiveNetwork = null;
}
- if (mScheduledHandleNetworkLostTimeout != null
- && !mScheduledHandleNetworkLostTimeout.isCancelled()
- && !mScheduledHandleNetworkLostTimeout.isDone()) {
+ if (mScheduledHandleNetworkLostFuture != null) {
final IllegalStateException exception =
new IllegalStateException(
- "Found a pending mScheduledHandleNetworkLostTimeout");
+ "Found a pending mScheduledHandleNetworkLostFuture");
Log.i(
TAG,
"Unexpected error in onDefaultNetworkLost. Tear down session",
@@ -3185,13 +3207,26 @@
+ " on session with token "
+ mCurrentToken);
+ final int token = mCurrentToken;
// Delay the teardown in case a new network will be available soon. For example,
// during handover between two WiFi networks, Android will disconnect from the
// first WiFi and then connects to the second WiFi.
- mScheduledHandleNetworkLostTimeout =
+ mScheduledHandleNetworkLostFuture =
mExecutor.schedule(
() -> {
- handleSessionLost(null, network);
+ if (isActiveToken(token)) {
+ handleSessionLost(null, network);
+ } else {
+ Log.d(
+ TAG,
+ "Scheduled handleSessionLost fired for "
+ + "obsolete token "
+ + token);
+ }
+
+ // Reset mScheduledHandleNetworkLostFuture since it's
+ // already run on executor thread.
+ mScheduledHandleNetworkLostFuture = null;
},
NETWORK_LOST_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
@@ -3202,28 +3237,26 @@
}
private void cancelHandleNetworkLostTimeout() {
- if (mScheduledHandleNetworkLostTimeout != null
- && !mScheduledHandleNetworkLostTimeout.isDone()) {
+ if (mScheduledHandleNetworkLostFuture != null) {
// It does not matter what to put in #cancel(boolean), because it is impossible
- // that the task tracked by mScheduledHandleNetworkLostTimeout is
+ // that the task tracked by mScheduledHandleNetworkLostFuture is
// in-progress since both that task and onDefaultNetworkChanged are submitted to
// mExecutor who has only one thread.
Log.d(TAG, "Cancel the task for handling network lost timeout");
- mScheduledHandleNetworkLostTimeout.cancel(false /* mayInterruptIfRunning */);
- mScheduledHandleNetworkLostTimeout = null;
+ mScheduledHandleNetworkLostFuture.cancel(false /* mayInterruptIfRunning */);
+ mScheduledHandleNetworkLostFuture = null;
}
}
private void cancelRetryNewIkeSessionFuture() {
- if (mScheduledHandleRetryIkeSessionTimeout != null
- && !mScheduledHandleRetryIkeSessionTimeout.isDone()) {
+ if (mScheduledHandleRetryIkeSessionFuture != null) {
// It does not matter what to put in #cancel(boolean), because it is impossible
- // that the task tracked by mScheduledHandleRetryIkeSessionTimeout is
+ // that the task tracked by mScheduledHandleRetryIkeSessionFuture is
// in-progress since both that task and onDefaultNetworkChanged are submitted to
// mExecutor who has only one thread.
Log.d(TAG, "Cancel the task for handling new ike session timeout");
- mScheduledHandleRetryIkeSessionTimeout.cancel(false /* mayInterruptIfRunning */);
- mScheduledHandleRetryIkeSessionTimeout = null;
+ mScheduledHandleRetryIkeSessionFuture.cancel(false /* mayInterruptIfRunning */);
+ mScheduledHandleRetryIkeSessionFuture = null;
}
}
@@ -3263,7 +3296,7 @@
}
private void handleSessionLost(@Nullable Exception exception, @Nullable Network network) {
- // Cancel mScheduledHandleNetworkLostTimeout if the session it is going to terminate is
+ // Cancel mScheduledHandleNetworkLostFuture if the session it is going to terminate is
// already terminated due to other failures.
cancelHandleNetworkLostTimeout();
@@ -4015,7 +4048,9 @@
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
case VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS:
mVpnRunner =
- new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile));
+ new IkeV2VpnRunner(
+ Ikev2VpnProfile.fromVpnProfile(profile),
+ mDeps.newScheduledThreadPoolExecutor());
mVpnRunner.start();
break;
default:
@@ -4191,22 +4226,48 @@
* @hide
*/
@VisibleForTesting
+ public static class IkeSessionWrapper {
+ private final IkeSession mImpl;
+
+ /** Create an IkeSessionWrapper */
+ public IkeSessionWrapper(IkeSession session) {
+ mImpl = session;
+ }
+
+ /** Update the underlying network of the IKE Session */
+ public void setNetwork(@NonNull Network network) {
+ mImpl.setNetwork(network);
+ }
+
+ /** Forcibly terminate the IKE Session */
+ public void kill() {
+ mImpl.kill();
+ }
+ }
+
+ /**
+ * Proxy to allow testing
+ *
+ * @hide
+ */
+ @VisibleForTesting
public static class Ikev2SessionCreator {
/** Creates a IKE session */
- public IkeSession createIkeSession(
+ public IkeSessionWrapper createIkeSession(
@NonNull Context context,
@NonNull IkeSessionParams ikeSessionParams,
@NonNull ChildSessionParams firstChildSessionParams,
@NonNull Executor userCbExecutor,
@NonNull IkeSessionCallback ikeSessionCallback,
@NonNull ChildSessionCallback firstChildSessionCallback) {
- return new IkeSession(
- context,
- ikeSessionParams,
- firstChildSessionParams,
- userCbExecutor,
- ikeSessionCallback,
- firstChildSessionCallback);
+ return new IkeSessionWrapper(
+ new IkeSession(
+ context,
+ ikeSessionParams,
+ firstChildSessionParams,
+ userCbExecutor,
+ ikeSessionCallback,
+ firstChildSessionCallback));
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 409ca03..701ac73c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -88,6 +88,7 @@
import android.provider.Settings;
import android.security.GateKeeper;
import android.service.gatekeeper.IGateKeeperService;
+import android.service.voice.VoiceInteractionManagerInternal;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -4252,6 +4253,11 @@
Binder.withCleanCallingIdentity(() -> {
mPm.onNewUserCreated(preCreatedUser.id, /* convertedFromPreCreated= */ true);
dispatchUserAdded(preCreatedUser, token);
+ VoiceInteractionManagerInternal vimi = LocalServices
+ .getService(VoiceInteractionManagerInternal.class);
+ if (vimi != null) {
+ vimi.onPreCreatedUserConversion(preCreatedUser.id);
+ }
});
return preCreatedUser;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1f3f039..4b58b82 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4746,9 +4746,6 @@
mPendingRemoteAnimation = options.getRemoteAnimationAdapter();
}
mPendingRemoteTransition = options.getRemoteTransition();
- // Since options gets sent to client apps, remove transition information from it.
- options.setRemoteTransition(null);
- options.setRemoteAnimationAdapter(null);
}
void applyOptionsAnimation() {
@@ -4969,8 +4966,12 @@
ActivityOptions takeOptions() {
if (DEBUG_TRANSITION) Slog.i(TAG, "Taking options for " + this + " callers="
+ Debug.getCallers(6));
+ if (mPendingOptions == null) return null;
final ActivityOptions opts = mPendingOptions;
mPendingOptions = null;
+ // Strip sensitive information from options before sending it to app.
+ opts.setRemoteTransition(null);
+ opts.setRemoteAnimationAdapter(null);
return opts;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index c8fcee6..2591a36 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2568,6 +2568,9 @@
}
task = r.getTask();
}
+ // If {@code isSystemCaller} is {@code true}, it means the user intends to stop
+ // pinned mode through UI; otherwise, it's called by an app and we need to stop
+ // locked or pinned mode, subject to checks.
getLockTaskController().stopLockTaskMode(task, isSystemCaller, callingUid);
}
// Launch in-call UI if a call is ongoing. This is necessary to allow stopping the lock
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 7a055d2..f11c2a7 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -481,27 +481,24 @@
*
* @param task the task that requested the end of lock task mode ({@code null} for quitting app
* pinning mode)
- * @param isSystemCaller indicates whether this request comes from the system via
- * {@link ActivityTaskManagerService#stopSystemLockTaskMode()}. If
- * {@code true}, it means the user intends to stop pinned mode through UI;
- * otherwise, it's called by an app and we need to stop locked or pinned
- * mode, subject to checks.
+ * @param stopAppPinning indicates whether to stop app pinning mode or to stop a task from
+ * being locked.
* @param callingUid the caller that requested the end of lock task mode.
* @throws IllegalArgumentException if the calling task is invalid (e.g., {@code null} or not in
* foreground)
* @throws SecurityException if the caller is not authorized to stop the lock task mode, i.e. if
* they differ from the one that launched lock task mode.
*/
- void stopLockTaskMode(@Nullable Task task, boolean isSystemCaller, int callingUid) {
+ void stopLockTaskMode(@Nullable Task task, boolean stopAppPinning, int callingUid) {
if (mLockTaskModeState == LOCK_TASK_MODE_NONE) {
return;
}
- if (isSystemCaller) {
+ if (stopAppPinning) {
if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
clearLockedTasks("stopAppPinning");
} else {
- Slog.e(TAG_LOCKTASK, "Attempted to stop LockTask with isSystemCaller=true");
+ Slog.e(TAG_LOCKTASK, "Attempted to stop app pinning while fully locked");
showLockTaskToast();
}
@@ -642,6 +639,10 @@
* @param callingUid the caller that requested the launch of lock task mode.
*/
void startLockTaskMode(@NonNull Task task, boolean isSystemCaller, int callingUid) {
+ if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
+ ProtoLog.w(WM_DEBUG_LOCKTASK, "startLockTaskMode: Can't lock due to auth");
+ return;
+ }
if (!isSystemCaller) {
task.mLockTaskUid = callingUid;
if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) {
@@ -654,6 +655,11 @@
statusBarManager.showScreenPinningRequest(task.mTaskId);
}
return;
+ } else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+ // startLockTask() called by app, and app is part of lock task allowlist.
+ // Deactivate the currently pinned task before doing so.
+ Slog.i(TAG, "Stop app pinning before entering full lock task mode");
+ stopLockTaskMode(/* task= */ null, /* stopAppPinning= */ true, callingUid);
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3b97e63..2ba0e23 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5328,7 +5328,23 @@
parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TASK ||
parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TOP ||
(destIntentFlags & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
- parent.deliverNewIntentLocked(callingUid, destIntent, destGrants, srec.packageName);
+ boolean abort;
+ try {
+ abort = !mTaskSupervisor.checkStartAnyActivityPermission(destIntent,
+ parent.info, null /* resultWho */, -1 /* requestCode */, srec.getPid(),
+ callingUid, srec.info.packageName, null /* callingFeatureId */,
+ false /* ignoreTargetSecurity */, false /* launchingInTask */, srec.app,
+ null /* resultRecord */, null /* resultRootTask */);
+ } catch (SecurityException e) {
+ abort = true;
+ }
+ if (abort) {
+ Slog.e(TAG, "Cannot navigateUpTo, intent =" + destIntent);
+ foundParentInTask = false;
+ } else {
+ parent.deliverNewIntentLocked(callingUid, destIntent, destGrants,
+ srec.packageName);
+ }
} else {
try {
ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 602579f..a4d6f70 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -17,6 +17,12 @@
package com.android.server.wm;
import static android.window.TaskFragmentOrganizer.putErrorInfoInBundle;
+import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_TO_TASK;
+import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
+import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
+import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
+import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
+import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -38,6 +44,7 @@
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentTransaction;
import com.android.internal.protolog.common.ProtoLog;
@@ -68,6 +75,11 @@
private final ArrayList<PendingTaskFragmentEvent> mPendingTaskFragmentEvents =
new ArrayList<>();
+ /** Map from {@link ITaskFragmentOrganizer} to {@link TaskFragmentTransaction}. */
+ private final ArrayMap<IBinder, TaskFragmentTransaction> mTmpOrganizerToTransactionMap =
+ new ArrayMap<>();
+ private final ArrayList<ITaskFragmentOrganizer> mTmpOrganizerList = new ArrayList<>();
+
TaskFragmentOrganizerController(ActivityTaskManagerService atm) {
mAtmService = atm;
mGlobalLock = atm.mGlobalLock;
@@ -145,107 +157,138 @@
mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/);
}
- void onTaskFragmentAppeared(TaskFragment tf) {
+ @NonNull
+ TaskFragmentTransaction.Change prepareTaskFragmentAppeared(@NonNull TaskFragment tf) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName());
final TaskFragmentInfo info = tf.getTaskFragmentInfo();
- try {
- mOrganizer.onTaskFragmentAppeared(info);
- mLastSentTaskFragmentInfos.put(tf, info);
- tf.mTaskFragmentAppearedSent = true;
- } catch (RemoteException e) {
- Slog.d(TAG, "Exception sending onTaskFragmentAppeared callback", e);
+ tf.mTaskFragmentAppearedSent = true;
+ mLastSentTaskFragmentInfos.put(tf, info);
+ final TaskFragmentTransaction.Change change =
+ new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_APPEARED)
+ .setTaskFragmentToken(tf.getFragmentToken())
+ .setTaskFragmentInfo(info);
+ if (shouldSendTaskFragmentParentInfoChanged(tf)) {
+ // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the same Task
+ final Task task = tf.getTask();
+ mLastSentTaskFragmentParentConfigs
+ .put(tf, new Configuration(task.getConfiguration()));
+ change.setTaskId(task.mTaskId)
+ .setTaskConfiguration(task.getConfiguration());
}
- onTaskFragmentParentInfoChanged(tf);
+ return change;
}
- void onTaskFragmentVanished(TaskFragment tf) {
+ @NonNull
+ TaskFragmentTransaction.Change prepareTaskFragmentVanished(@NonNull TaskFragment tf) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment vanished name=%s", tf.getName());
- try {
- mOrganizer.onTaskFragmentVanished(tf.getTaskFragmentInfo());
- } catch (RemoteException e) {
- Slog.d(TAG, "Exception sending onTaskFragmentVanished callback", e);
- }
tf.mTaskFragmentAppearedSent = false;
mLastSentTaskFragmentInfos.remove(tf);
mLastSentTaskFragmentParentConfigs.remove(tf);
+ return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_VANISHED)
+ .setTaskFragmentToken(tf.getFragmentToken())
+ .setTaskFragmentInfo(tf.getTaskFragmentInfo());
}
- void onTaskFragmentInfoChanged(TaskFragment tf) {
- // Parent config may have changed. The controller will check if there is any important
- // config change for the organizer.
- onTaskFragmentParentInfoChanged(tf);
-
+ @Nullable
+ TaskFragmentTransaction.Change prepareTaskFragmentInfoChanged(
+ @NonNull TaskFragment tf) {
// Check if the info is different from the last reported info.
final TaskFragmentInfo info = tf.getTaskFragmentInfo();
final TaskFragmentInfo lastInfo = mLastSentTaskFragmentInfos.get(tf);
if (info.equalsForTaskFragmentOrganizer(lastInfo) && configurationsAreEqualForOrganizer(
info.getConfiguration(), lastInfo.getConfiguration())) {
- return;
+ // Parent config may have changed. The controller will check if there is any
+ // important config change for the organizer.
+ return prepareTaskFragmentParentInfoChanged(tf);
}
+
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment info changed name=%s",
tf.getName());
- try {
- mOrganizer.onTaskFragmentInfoChanged(info);
- mLastSentTaskFragmentInfos.put(tf, info);
- } catch (RemoteException e) {
- Slog.d(TAG, "Exception sending onTaskFragmentInfoChanged callback", e);
+ mLastSentTaskFragmentInfos.put(tf, info);
+ final TaskFragmentTransaction.Change change =
+ new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_INFO_CHANGED)
+ .setTaskFragmentToken(tf.getFragmentToken())
+ .setTaskFragmentInfo(info);
+ if (shouldSendTaskFragmentParentInfoChanged(tf)) {
+ // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the same Task
+ // at once.
+ // Parent config may have changed. The controller will check if there is any
+ // important config change for the organizer.
+ final Task task = tf.getTask();
+ mLastSentTaskFragmentParentConfigs
+ .put(tf, new Configuration(task.getConfiguration()));
+ change.setTaskId(task.mTaskId)
+ .setTaskConfiguration(task.getConfiguration());
}
+ return change;
}
- void onTaskFragmentParentInfoChanged(TaskFragment tf) {
- // Check if the parent info is different from the last reported parent info.
- if (tf.getParent() == null || tf.getParent().asTask() == null) {
- mLastSentTaskFragmentParentConfigs.remove(tf);
- return;
+ @Nullable
+ TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged(
+ @NonNull TaskFragment tf) {
+ if (!shouldSendTaskFragmentParentInfoChanged(tf)) {
+ return null;
}
- final Task parent = tf.getParent().asTask();
+
+ final Task parent = tf.getTask();
final Configuration parentConfig = parent.getConfiguration();
- final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(tf);
- if (configurationsAreEqualForOrganizer(parentConfig, lastParentConfig)
- && parentConfig.windowConfiguration.getWindowingMode()
- == lastParentConfig.windowConfiguration.getWindowingMode()) {
- return;
- }
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"TaskFragment parent info changed name=%s parentTaskId=%d",
tf.getName(), parent.mTaskId);
- try {
- mOrganizer.onTaskFragmentParentInfoChanged(tf.getFragmentToken(), parentConfig);
- mLastSentTaskFragmentParentConfigs.put(tf, new Configuration(parentConfig));
- } catch (RemoteException e) {
- Slog.d(TAG, "Exception sending onTaskFragmentParentInfoChanged callback", e);
- }
+ mLastSentTaskFragmentParentConfigs.put(tf, new Configuration(parentConfig));
+ return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
+ .setTaskFragmentToken(tf.getFragmentToken())
+ .setTaskId(parent.mTaskId)
+ .setTaskConfiguration(parent.getConfiguration());
}
- void onTaskFragmentError(IBinder errorCallbackToken, @Nullable TaskFragment taskFragment,
- int opType, Throwable exception) {
+ /** Whether the system should report TaskFragment parent info changed to the organizer. */
+ private boolean shouldSendTaskFragmentParentInfoChanged(@NonNull TaskFragment tf) {
+ final Task parent = tf.getTask();
+ if (parent == null) {
+ // The TaskFragment is not attached.
+ mLastSentTaskFragmentParentConfigs.remove(tf);
+ return false;
+ }
+ // Check if the parent info is different from the last reported parent info.
+ final Configuration parentConfig = parent.getConfiguration();
+ final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(tf);
+ return !configurationsAreEqualForOrganizer(parentConfig, lastParentConfig)
+ || parentConfig.windowConfiguration.getWindowingMode()
+ != lastParentConfig.windowConfiguration.getWindowingMode();
+ }
+
+ @NonNull
+ TaskFragmentTransaction.Change prepareTaskFragmentError(
+ @Nullable IBinder errorCallbackToken, @Nullable TaskFragment taskFragment,
+ int opType, @NonNull Throwable exception) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"Sending TaskFragment error exception=%s", exception.toString());
final TaskFragmentInfo info =
taskFragment != null ? taskFragment.getTaskFragmentInfo() : null;
final Bundle errorBundle = putErrorInfoInBundle(exception, info, opType);
- try {
- mOrganizer.onTaskFragmentError(errorCallbackToken, errorBundle);
- } catch (RemoteException e) {
- Slog.d(TAG, "Exception sending onTaskFragmentError callback", e);
- }
+ return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_ERROR)
+ .setErrorCallbackToken(errorCallbackToken)
+ .setErrorBundle(errorBundle);
}
- void onActivityReparentToTask(ActivityRecord activity) {
+ @Nullable
+ TaskFragmentTransaction.Change prepareActivityReparentToTask(
+ @NonNull ActivityRecord activity) {
if (activity.finishing) {
Slog.d(TAG, "Reparent activity=" + activity.token + " is finishing");
- return;
+ return null;
}
final Task task = activity.getTask();
if (task == null || task.effectiveUid != mOrganizerUid) {
Slog.d(TAG, "Reparent activity=" + activity.token
+ " is not in a task belong to the organizer app.");
- return;
+ return null;
}
if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) {
Slog.d(TAG, "Reparent activity=" + activity.token
+ " is not allowed to be embedded.");
- return;
+ return null;
}
final IBinder activityToken;
@@ -268,11 +311,10 @@
}
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Activity=%s reparent to taskId=%d",
activity.token, task.mTaskId);
- try {
- mOrganizer.onActivityReparentToTask(task.mTaskId, activity.intent, activityToken);
- } catch (RemoteException e) {
- Slog.d(TAG, "Exception sending onActivityReparentToTask callback", e);
- }
+ return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENT_TO_TASK)
+ .setTaskId(task.mTaskId)
+ .setActivityIntent(activity.intent)
+ .setActivityToken(activityToken);
}
}
@@ -375,7 +417,7 @@
*/
@Nullable
public RemoteAnimationDefinition getRemoteAnimationDefinition(
- ITaskFragmentOrganizer organizer, int taskId) {
+ @NonNull ITaskFragmentOrganizer organizer, int taskId) {
synchronized (mGlobalLock) {
final TaskFragmentOrganizerState organizerState =
mTaskFragmentOrganizerState.get(organizer.asBinder());
@@ -385,12 +427,13 @@
}
}
- int getTaskFragmentOrganizerUid(ITaskFragmentOrganizer organizer) {
+ int getTaskFragmentOrganizerUid(@NonNull ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
return state.mOrganizerUid;
}
- void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) {
+ void onTaskFragmentAppeared(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull TaskFragment taskFragment) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
if (!state.addTaskFragment(taskFragment)) {
return;
@@ -406,19 +449,20 @@
}
}
- void onTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) {
+ void onTaskFragmentInfoChanged(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull TaskFragment taskFragment) {
handleTaskFragmentInfoChanged(organizer, taskFragment,
PendingTaskFragmentEvent.EVENT_INFO_CHANGED);
}
- void onTaskFragmentParentInfoChanged(ITaskFragmentOrganizer organizer,
- TaskFragment taskFragment) {
+ void onTaskFragmentParentInfoChanged(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull TaskFragment taskFragment) {
handleTaskFragmentInfoChanged(organizer, taskFragment,
PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED);
}
- private void handleTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer,
- TaskFragment taskFragment, int eventType) {
+ private void handleTaskFragmentInfoChanged(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull TaskFragment taskFragment, int eventType) {
validateAndGetState(organizer);
if (!taskFragment.mTaskFragmentAppearedSent) {
// Skip if TaskFragment still not appeared.
@@ -444,7 +488,8 @@
mPendingTaskFragmentEvents.add(pendingEvent);
}
- void onTaskFragmentVanished(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) {
+ void onTaskFragmentVanished(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull TaskFragment taskFragment) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i);
@@ -467,8 +512,9 @@
state.removeTaskFragment(taskFragment);
}
- void onTaskFragmentError(ITaskFragmentOrganizer organizer, IBinder errorCallbackToken,
- TaskFragment taskFragment, int opType, Throwable exception) {
+ void onTaskFragmentError(@NonNull ITaskFragmentOrganizer organizer,
+ @Nullable IBinder errorCallbackToken, @Nullable TaskFragment taskFragment,
+ int opType, @NonNull Throwable exception) {
validateAndGetState(organizer);
Slog.w(TAG, "onTaskFragmentError ", exception);
final PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent.Builder(
@@ -483,7 +529,7 @@
mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal();
}
- void onActivityReparentToTask(ActivityRecord activity) {
+ void onActivityReparentToTask(@NonNull ActivityRecord activity) {
final ITaskFragmentOrganizer organizer;
if (activity.mLastTaskFragmentOrganizerBeforePip != null) {
// If the activity is previously embedded in an organized TaskFragment.
@@ -515,11 +561,11 @@
mPendingTaskFragmentEvents.add(pendingEvent);
}
- boolean isOrganizerRegistered(ITaskFragmentOrganizer organizer) {
+ boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) {
return mTaskFragmentOrganizerState.containsKey(organizer.asBinder());
}
- private void removeOrganizer(ITaskFragmentOrganizer organizer) {
+ private void removeOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
// remove all of the children of the organized TaskFragment
state.dispose();
@@ -539,7 +585,9 @@
* we wouldn't register {@link DeathRecipient} for the organizer, and might not remove the
* {@link TaskFragment} after the organizer process died.
*/
- private TaskFragmentOrganizerState validateAndGetState(ITaskFragmentOrganizer organizer) {
+ @NonNull
+ private TaskFragmentOrganizerState validateAndGetState(
+ @NonNull ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state =
mTaskFragmentOrganizerState.get(organizer.asBinder());
if (state == null) {
@@ -672,7 +720,7 @@
}
@Nullable
- private PendingTaskFragmentEvent getLastPendingLifecycleEvent(TaskFragment tf) {
+ private PendingTaskFragmentEvent getLastPendingLifecycleEvent(@NonNull TaskFragment tf) {
for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i);
if (tf == entry.mTaskFragment && entry.isLifecycleEvent()) {
@@ -683,7 +731,7 @@
}
@Nullable
- private PendingTaskFragmentEvent getPendingTaskFragmentEvent(TaskFragment taskFragment,
+ private PendingTaskFragmentEvent getPendingTaskFragmentEvent(@NonNull TaskFragment taskFragment,
int type) {
for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i);
@@ -731,16 +779,36 @@
candidateEvents.add(event);
}
final int numEvents = candidateEvents.size();
+ if (numEvents == 0) {
+ return;
+ }
+
+ mTmpOrganizerToTransactionMap.clear();
+ mTmpOrganizerList.clear();
for (int i = 0; i < numEvents; i++) {
- dispatchEvent(candidateEvents.get(i));
+ final PendingTaskFragmentEvent event = candidateEvents.get(i);
+ if (!mTmpOrganizerToTransactionMap.containsKey(event.mTaskFragmentOrg.asBinder())) {
+ mTmpOrganizerToTransactionMap.put(event.mTaskFragmentOrg.asBinder(),
+ new TaskFragmentTransaction());
+ mTmpOrganizerList.add(event.mTaskFragmentOrg);
+ }
+ mTmpOrganizerToTransactionMap.get(event.mTaskFragmentOrg.asBinder())
+ .addChange(prepareChange(event));
}
- if (numEvents > 0) {
- mPendingTaskFragmentEvents.removeAll(candidateEvents);
+ final int numOrganizers = mTmpOrganizerList.size();
+ for (int i = 0; i < numOrganizers; i++) {
+ final ITaskFragmentOrganizer organizer = mTmpOrganizerList.get(i);
+ dispatchTransactionInfo(organizer,
+ mTmpOrganizerToTransactionMap.get(organizer.asBinder()));
}
+ mPendingTaskFragmentEvents.removeAll(candidateEvents);
+ mTmpOrganizerToTransactionMap.clear();
+ mTmpOrganizerList.clear();
}
- private static boolean isTaskVisible(Task task, ArrayList<Task> knownVisibleTasks,
- ArrayList<Task> knownInvisibleTasks) {
+ private static boolean isTaskVisible(@NonNull Task task,
+ @NonNull ArrayList<Task> knownVisibleTasks,
+ @NonNull ArrayList<Task> knownInvisibleTasks) {
if (knownVisibleTasks.contains(task)) {
return true;
}
@@ -756,44 +824,57 @@
}
}
- void dispatchPendingInfoChangedEvent(TaskFragment taskFragment) {
- PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment,
+ void dispatchPendingInfoChangedEvent(@NonNull TaskFragment taskFragment) {
+ final PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment,
PendingTaskFragmentEvent.EVENT_INFO_CHANGED);
if (event == null) {
return;
}
- dispatchEvent(event);
+ final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
+ transaction.addChange(prepareChange(event));
+ dispatchTransactionInfo(event.mTaskFragmentOrg, transaction);
mPendingTaskFragmentEvents.remove(event);
}
- private void dispatchEvent(PendingTaskFragmentEvent event) {
+ private void dispatchTransactionInfo(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull TaskFragmentTransaction transaction) {
+ if (transaction.isEmpty()) {
+ return;
+ }
+ try {
+ organizer.onTransactionReady(transaction);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Exception sending TaskFragmentTransaction", e);
+ }
+ }
+
+ @Nullable
+ private TaskFragmentTransaction.Change prepareChange(
+ @NonNull PendingTaskFragmentEvent event) {
final ITaskFragmentOrganizer taskFragmentOrg = event.mTaskFragmentOrg;
final TaskFragment taskFragment = event.mTaskFragment;
final TaskFragmentOrganizerState state =
mTaskFragmentOrganizerState.get(taskFragmentOrg.asBinder());
if (state == null) {
- return;
+ return null;
}
switch (event.mEventType) {
case PendingTaskFragmentEvent.EVENT_APPEARED:
- state.onTaskFragmentAppeared(taskFragment);
- break;
+ return state.prepareTaskFragmentAppeared(taskFragment);
case PendingTaskFragmentEvent.EVENT_VANISHED:
- state.onTaskFragmentVanished(taskFragment);
- break;
+ return state.prepareTaskFragmentVanished(taskFragment);
case PendingTaskFragmentEvent.EVENT_INFO_CHANGED:
- state.onTaskFragmentInfoChanged(taskFragment);
- break;
+ return state.prepareTaskFragmentInfoChanged(taskFragment);
case PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED:
- state.onTaskFragmentParentInfoChanged(taskFragment);
- break;
+ return state.prepareTaskFragmentParentInfoChanged(taskFragment);
case PendingTaskFragmentEvent.EVENT_ERROR:
- state.onTaskFragmentError(event.mErrorCallbackToken, taskFragment, event.mOpType,
- event.mException);
- break;
+ return state.prepareTaskFragmentError(event.mErrorCallbackToken, taskFragment,
+ event.mOpType, event.mException);
case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK:
- state.onActivityReparentToTask(event.mActivity);
+ return state.prepareActivityReparentToTask(event.mActivity);
+ default:
+ throw new IllegalArgumentException("Unknown TaskFragmentEvent=" + event.mEventType);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index 5f9f1b2..9c833c0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -24,6 +24,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sSystemClock;
@@ -185,12 +186,15 @@
JobStatus js = JobStatus.createFromJobInfo(
jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
js.serviceInfo = mock(ServiceInfo.class);
+ js.setStandbyBucket(FREQUENT_INDEX);
// Make sure Doze and background-not-restricted don't affect tests.
js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(),
/* state */ true, /* allowlisted */false);
js.setBackgroundNotRestrictedConstraintSatisfied(
sElapsedRealtimeClock.millis(), true, false);
+ js.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
js.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+ js.setExpeditedJobQuotaApproved(sElapsedRealtimeClock.millis(), true);
js.setExpeditedJobTareApproved(sElapsedRealtimeClock.millis(), true);
return js;
}
@@ -294,6 +298,7 @@
verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
assertFalse(job.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertFalse(job.isReady());
}
@Test
@@ -309,6 +314,7 @@
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
assertTrue(job.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(job.isReady());
}
@Test
@@ -331,19 +337,25 @@
inOrder.verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS))
.onControllerStateChanged(any());
assertTrue(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobPending.isReady());
assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobRunning.isReady());
setUidBias(uid, JobInfo.BIAS_TOP_APP);
// Processing happens on the handler, so wait until we're sure the change has been processed
inOrder.verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS))
.onControllerStateChanged(any());
// Already running job should continue but pending job must wait.
assertFalse(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertFalse(jobPending.isReady());
assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobRunning.isReady());
setUidBias(uid, JobInfo.BIAS_DEFAULT);
inOrder.verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS))
.onControllerStateChanged(any());
assertTrue(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobPending.isReady());
assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobRunning.isReady());
}
@Test
@@ -367,11 +379,13 @@
verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
assertFalse(jobNonWidget.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertFalse(jobNonWidget.isReady());
when(appWidgetManager.isBoundWidgetPackage(SOURCE_PACKAGE, SOURCE_USER_ID))
.thenReturn(true);
trackJobs(jobWidget);
assertTrue(jobWidget.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobWidget.isReady());
}
@Test
@@ -390,6 +404,7 @@
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobStatus.isReady());
mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
SOURCE_PACKAGE, sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
@@ -401,6 +416,7 @@
anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS),
anyLong(), eq(TAG_PREFETCH), any(), any());
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertFalse(jobStatus.isReady());
}
@Test
@@ -418,6 +434,7 @@
inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertFalse(jobStatus.isReady());
mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
SOURCE_PACKAGE, sSystemClock.millis() + MINUTE_IN_MILLIS);
@@ -426,6 +443,7 @@
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobStatus.isReady());
}
@Test
@@ -448,6 +466,7 @@
anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS),
anyLong(), eq(TAG_PREFETCH), any(), any());
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertFalse(jobStatus.isReady());
mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
SOURCE_PACKAGE, sSystemClock.millis() + HOUR_IN_MILLIS);
@@ -456,6 +475,7 @@
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobStatus.isReady());
sSystemClock = getShiftedClock(sSystemClock, HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index eba2755..f38731b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -199,9 +199,11 @@
final Task parent = mock(Task.class);
final Configuration parentConfig = new Configuration();
parentConfig.smallestScreenWidthDp = 10;
- doReturn(parent).when(mTaskFragment).getParent();
+ doReturn(parent).when(mTaskFragment).getTask();
doReturn(parentConfig).when(parent).getConfiguration();
- doReturn(parent).when(parent).asTask();
+ // Task needs to be visible
+ parent.lastActiveTime = 100;
+ doReturn(true).when(parent).shouldBeVisible(any());
mTaskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentParentInfoChanged(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index bcfee82..0ce0265 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -102,6 +102,7 @@
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.android.server.soundtrigger.SoundTriggerInternal;
+import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -134,18 +135,21 @@
private final RemoteCallbackList<IVoiceInteractionSessionListener>
mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
+ // TODO(b/226201975): remove once RoleService supports pre-created users
+ private final ArrayList<UserHandle> mIgnoredPreCreatedUsers = new ArrayList<>();
+
public VoiceInteractionManagerService(Context context) {
super(context);
mContext = context;
mResolver = context.getContentResolver();
+ mUserManagerInternal = Objects.requireNonNull(
+ LocalServices.getService(UserManagerInternal.class));
mDbHelper = new DatabaseHelper(context);
mServiceStub = new VoiceInteractionManagerServiceStub();
mAmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityManagerInternal.class));
mAtmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityTaskManagerInternal.class));
- mUserManagerInternal = Objects.requireNonNull(
- LocalServices.getService(UserManagerInternal.class));
LegacyPermissionManagerInternal permissionManagerInternal = LocalServices.getService(
LegacyPermissionManagerInternal.class);
@@ -300,6 +304,25 @@
}
return hotwordDetectionConnection.mIdentity;
}
+
+ @Override
+ public void onPreCreatedUserConversion(int userId) {
+ Slogf.d(TAG, "onPreCreatedUserConversion(%d)", userId);
+
+ for (int i = 0; i < mIgnoredPreCreatedUsers.size(); i++) {
+ UserHandle preCreatedUser = mIgnoredPreCreatedUsers.get(i);
+ if (preCreatedUser.getIdentifier() == userId) {
+ Slogf.d(TAG, "Updating role on pre-created user %d", userId);
+ mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
+ preCreatedUser);
+ mIgnoredPreCreatedUsers.remove(i);
+ return;
+ }
+ }
+ Slogf.w(TAG, "onPreCreatedUserConversion(%d): not available on "
+ + "mIgnoredPreCreatedUserIds (%s)", userId, mIgnoredPreCreatedUsers);
+ }
+
}
// implementation entry point and binder service
@@ -317,10 +340,12 @@
private boolean mTemporarilyDisabled;
private final boolean mEnableService;
+ // TODO(b/226201975): remove reference once RoleService supports pre-created users
+ private final RoleObserver mRoleObserver;
VoiceInteractionManagerServiceStub() {
mEnableService = shouldEnableService(mContext);
- new RoleObserver(mContext.getMainExecutor());
+ mRoleObserver = new RoleObserver(mContext.getMainExecutor());
}
void handleUserStop(String packageName, int userHandle) {
@@ -1884,6 +1909,7 @@
pw.println(" mTemporarilyDisabled: " + mTemporarilyDisabled);
pw.println(" mCurUser: " + mCurUser);
pw.println(" mCurUserSupported: " + mCurUserSupported);
+ pw.println(" mIgnoredPreCreatedUsers: " + mIgnoredPreCreatedUsers);
dumpSupportedUsers(pw, " ");
mDbHelper.dump(pw);
if (mImpl == null) {
@@ -1997,6 +2023,23 @@
List<String> roleHolders = mRm.getRoleHoldersAsUser(roleName, user);
+ // TODO(b/226201975): this method is beling called when a pre-created user is added,
+ // at which point it doesn't have any role holders. But it's not called again when
+ // the actual user is added (i.e., when the pre-created user is converted), so we
+ // need to save the user id and call this method again when it's converted
+ // (at onPreCreatedUserConversion()).
+ // Once RoleService properly handles pre-created users, this workaround should be
+ // removed.
+ if (roleHolders.isEmpty()) {
+ UserInfo userInfo = mUserManagerInternal.getUserInfo(user.getIdentifier());
+ if (userInfo != null && userInfo.preCreated) {
+ Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now",
+ userInfo.toFullString());
+ mIgnoredPreCreatedUsers.add(user);
+ return;
+ }
+ }
+
int userId = user.getIdentifier();
if (roleHolders.isEmpty()) {
Settings.Secure.putStringForUser(getContext().getContentResolver(),
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index 4469ffc..7eec86a 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -115,15 +115,15 @@
/** @hide */
public static @RadioAccessNetworkType int fromString(@NonNull String str) {
switch (str.toUpperCase()) {
- case "GERAN" : return GERAN;
- case "UTRAN" : return UTRAN;
- case "EUTRAN" : return EUTRAN;
- case "CDMA2000" : return CDMA2000;
- case "IWLAN" : return IWLAN;
- case "NGRAN" : return NGRAN;
+ case "UNKNOWN": return UNKNOWN;
+ case "GERAN": return GERAN;
+ case "UTRAN": return UTRAN;
+ case "EUTRAN": return EUTRAN;
+ case "CDMA2000": return CDMA2000;
+ case "IWLAN": return IWLAN;
+ case "NGRAN": return NGRAN;
default:
- Rlog.e(TAG, "Invalid access network type " + str);
- return UNKNOWN;
+ throw new IllegalArgumentException("Invalid access network type " + str);
}
}
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 70fe6b1..e032f65 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8550,6 +8550,13 @@
* IWLAN handover rules that determine whether handover is allowed or disallowed between
* cellular and IWLAN.
*
+ * Rule syntax: "source=[GERAN|UTRAN|EUTRAN|NGRAN|IWLAN|UNKNOWN], target=[GERAN|UTRAN|EUTRAN
+ * |NGRAN|IWLAN], type=[allowed|disallowed], roaming=[true|false], capabilities=[INTERNET|MMS
+ * |FOTA|IMS|CBS|SUPL|EIMS|XCAP|DUN]"
+ *
+ * Note that UNKNOWN can be only specified in the source access network and can be only used
+ * in the disallowed rule.
+ *
* The handover rules will be matched in the order. Here are some sample rules.
* <string-array name="iwlan_handover_rules" num="5">
* <!-- Handover from IWLAN to 2G/3G is not allowed -->