Merge "Import translations. DO NOT MERGE ANYWHERE" into tm-qpr-dev
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index df1c0d7..e5243ee 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -878,8 +878,8 @@
* <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code VIDEO_CALL}</td> <td colspan="3" id="rb"></td> <td>Preview with video call</td> </tr>
* <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code PREVIEW_VIDEO_STILL}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>Multi-purpose stream with JPEG or YUV still capture</td> </tr>
* <tr> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>YUV and JPEG concurrent still image capture (for testing)</td> </tr>
- * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, video record and JPEG or YUV video snapshot</td> </tr>
- * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, in-application image processing, and JPEG or YUV still image capture</td> </tr>
+ * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td>{@code JPEG}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, video record and JPEG video snapshot</td> </tr>
+ * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, in-application image processing, and JPEG still image capture</td> </tr>
* </table><br>
* </p>
*
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index e5b9cdb..754472f 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -1223,14 +1223,6 @@
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
STREAM_USE_CASE_PREVIEW),
- new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD,
- STREAM_USE_CASE_RECORD),
- new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
- STREAM_USE_CASE_STILL_CAPTURE)},
- "Preview, video record and YUV video snapshot"),
- new StreamCombinationTemplate(new StreamTemplate [] {
- new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
- STREAM_USE_CASE_PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
STREAM_USE_CASE_RECORD),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD,
@@ -1239,27 +1231,11 @@
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
STREAM_USE_CASE_PREVIEW),
- new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
- STREAM_USE_CASE_RECORD),
- new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
- STREAM_USE_CASE_STILL_CAPTURE)},
- "Preview, in-application video processing and YUV video snapshot"),
- new StreamCombinationTemplate(new StreamTemplate [] {
- new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
- STREAM_USE_CASE_PREVIEW),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
STREAM_USE_CASE_PREVIEW),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM,
STREAM_USE_CASE_STILL_CAPTURE)},
"Preview, in-application image processing, and JPEG still image capture"),
- new StreamCombinationTemplate(new StreamTemplate [] {
- new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
- STREAM_USE_CASE_PREVIEW),
- new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
- STREAM_USE_CASE_PREVIEW),
- new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM,
- STREAM_USE_CASE_STILL_CAPTURE)},
- "Preview, in-application image processing, and YUV still image capture"),
};
private static StreamCombinationTemplate sPreviewStabilizedStreamCombinations[] = {
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index bf5b970..6e4535b 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -36,39 +36,101 @@
public abstract class DreamOverlayService extends Service {
private static final String TAG = "DreamOverlayService";
private static final boolean DEBUG = false;
- private boolean mShowComplications;
- private ComponentName mDreamComponent;
- private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
+ // The last client that started dreaming and hasn't ended
+ private OverlayClient mCurrentClient;
+
+ // An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
+ // requests to the {@link DreamOverlayService}
+ private static class OverlayClient extends IDreamOverlayClient.Stub {
+ private final DreamOverlayService mService;
+ private boolean mShowComplications;
+ private ComponentName mDreamComponent;
+ IDreamOverlayCallback mDreamOverlayCallback;
+
+ OverlayClient(DreamOverlayService service) {
+ mService = service;
+ }
+
@Override
- public void startDream(WindowManager.LayoutParams layoutParams,
- IDreamOverlayCallback callback, String dreamComponent,
- boolean shouldShowComplications) {
- mDreamOverlayCallback = callback;
+ public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback,
+ String dreamComponent, boolean shouldShowComplications) throws RemoteException {
mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
mShowComplications = shouldShowComplications;
- onStartDream(layoutParams);
+ mDreamOverlayCallback = callback;
+ mService.startDream(this, params);
+ }
+
+
+
+ @Override
+ public void wakeUp() {
+ mService.wakeUp(this, () -> {
+ try {
+ mDreamOverlayCallback.onWakeUpComplete();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not notify dream of wakeUp", e);
+ }
+ });
}
@Override
public void endDream() {
- onEndDream();
+ mService.endDream(this);
}
+ private void onExitRequested() {
+ try {
+ mDreamOverlayCallback.onExitRequested();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not request exit:" + e);
+ }
+ }
+
+ private boolean shouldShowComplications() {
+ return mShowComplications;
+ }
+
+ private ComponentName getComponent() {
+ return mDreamComponent;
+ }
+ }
+
+ private void startDream(OverlayClient client, WindowManager.LayoutParams params) {
+ endDream(mCurrentClient);
+ mCurrentClient = client;
+ onStartDream(params);
+ }
+
+ private void endDream(OverlayClient client) {
+ if (client == null || client != mCurrentClient) {
+ return;
+ }
+
+ onEndDream();
+ mCurrentClient = null;
+ }
+
+ private void wakeUp(OverlayClient client, Runnable callback) {
+ if (mCurrentClient != client) {
+ return;
+ }
+
+ onWakeUp(callback);
+ }
+
+ private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
@Override
- public void wakeUp() {
- onWakeUp(() -> {
- try {
- mDreamOverlayCallback.onWakeUpComplete();
- } catch (RemoteException e) {
- Log.e(TAG, "Could not notify dream of wakeUp:" + e);
- }
- });
+ public void getClient(IDreamOverlayClientCallback callback) {
+ try {
+ callback.onDreamOverlayClient(
+ new OverlayClient(DreamOverlayService.this));
+ } catch (RemoteException e) {
+ Log.e(TAG, "could not send client to callback", e);
+ }
}
};
- IDreamOverlayCallback mDreamOverlayCallback;
-
public DreamOverlayService() {
}
@@ -110,18 +172,23 @@
* This method is invoked to request the dream exit.
*/
public final void requestExit() {
- try {
- mDreamOverlayCallback.onExitRequested();
- } catch (RemoteException e) {
- Log.e(TAG, "Could not request exit:" + e);
+ if (mCurrentClient == null) {
+ throw new IllegalStateException("requested exit with no dream present");
}
+
+ mCurrentClient.onExitRequested();
}
/**
* Returns whether to show complications on the dream overlay.
*/
public final boolean shouldShowComplications() {
- return mShowComplications;
+ if (mCurrentClient == null) {
+ throw new IllegalStateException(
+ "requested if should show complication when no dream active");
+ }
+
+ return mCurrentClient.shouldShowComplications();
}
/**
@@ -129,6 +196,10 @@
* @hide
*/
public final ComponentName getDreamComponent() {
- return mDreamComponent;
+ if (mCurrentClient == null) {
+ throw new IllegalStateException("requested dream component when no dream active");
+ }
+
+ return mCurrentClient.getComponent();
}
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index d378886..6a4710f 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -248,25 +248,39 @@
private OverlayConnection mOverlayConnection;
private static class OverlayConnection extends PersistentServiceConnection<IDreamOverlay> {
- // Overlay set during onBind.
- private IDreamOverlay mOverlay;
+ // Retrieved Client
+ private IDreamOverlayClient mClient;
+
// A list of pending requests to execute on the overlay.
- private final ArrayList<Consumer<IDreamOverlay>> mConsumers = new ArrayList<>();
+ private final ArrayList<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>();
+
+ private final IDreamOverlayClientCallback mClientCallback =
+ new IDreamOverlayClientCallback.Stub() {
+ @Override
+ public void onDreamOverlayClient(IDreamOverlayClient client) {
+ mClient = client;
+
+ for (Consumer<IDreamOverlayClient> consumer : mConsumers) {
+ consumer.accept(mClient);
+ }
+ }
+ };
private final Callback<IDreamOverlay> mCallback = new Callback<IDreamOverlay>() {
@Override
public void onConnected(ObservableServiceConnection<IDreamOverlay> connection,
IDreamOverlay service) {
- mOverlay = service;
- for (Consumer<IDreamOverlay> consumer : mConsumers) {
- consumer.accept(mOverlay);
+ try {
+ service.getClient(mClientCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "could not get DreamOverlayClient", e);
}
}
@Override
public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
int reason) {
- mOverlay = null;
+ mClient = null;
}
};
@@ -296,16 +310,16 @@
super.unbind();
}
- public void addConsumer(Consumer<IDreamOverlay> consumer) {
+ public void addConsumer(Consumer<IDreamOverlayClient> consumer) {
execute(() -> {
mConsumers.add(consumer);
- if (mOverlay != null) {
- consumer.accept(mOverlay);
+ if (mClient != null) {
+ consumer.accept(mClient);
}
});
}
- public void removeConsumer(Consumer<IDreamOverlay> consumer) {
+ public void removeConsumer(Consumer<IDreamOverlayClient> consumer) {
execute(() -> mConsumers.remove(consumer));
}
@@ -1050,6 +1064,24 @@
* </p>
*/
public final void finish() {
+ // If there is an active overlay connection, signal that the dream is ending before
+ // continuing. Note that the overlay cannot rely on the unbound state, since another dream
+ // might have bound to it in the meantime.
+ if (mOverlayConnection != null) {
+ mOverlayConnection.addConsumer(overlay -> {
+ try {
+ overlay.endDream();
+ mOverlayConnection.unbind();
+ mOverlayConnection = null;
+ finish();
+ } catch (RemoteException e) {
+ Log.e(mTag, "could not inform overlay of dream end:" + e);
+ }
+ });
+ mOverlayConnection.clearConsumers();
+ return;
+ }
+
if (mDebug) Slog.v(mTag, "finish(): mFinished=" + mFinished);
Activity activity = mActivity;
@@ -1066,10 +1098,6 @@
}
mFinished = true;
- if (mOverlayConnection != null) {
- mOverlayConnection.unbind();
- }
-
if (mDreamToken == null) {
if (mDebug) Slog.v(mTag, "finish() called when not attached.");
stopSelf();
@@ -1365,7 +1393,7 @@
mWindow.getDecorView().addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
- private Consumer<IDreamOverlay> mDreamStartOverlayConsumer;
+ private Consumer<IDreamOverlayClient> mDreamStartOverlayConsumer;
@Override
public void onViewAttachedToWindow(View v) {
@@ -1389,17 +1417,6 @@
@Override
public void onViewDetachedFromWindow(View v) {
- if (mOverlayConnection != null) {
- mOverlayConnection.addConsumer(overlay -> {
- try {
- overlay.endDream();
- } catch (RemoteException e) {
- Log.e(mTag, "could not inform overlay of dream end:" + e);
- }
- });
- mOverlayConnection.clearConsumers();
- }
-
if (mActivity == null || !mActivity.isChangingConfigurations()) {
// Only stop the dream if the view is not detached by relaunching
// activity for configuration changes. It is important to also clear
@@ -1408,6 +1425,10 @@
mActivity = null;
finish();
}
+
+ if (mOverlayConnection != null && mDreamStartOverlayConsumer != null) {
+ mOverlayConnection.removeConsumer(mDreamStartOverlayConsumer);
+ }
}
});
}
diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl
index 0e4bd3b..7ec75a5 100644
--- a/core/java/android/service/dreams/IDreamOverlay.aidl
+++ b/core/java/android/service/dreams/IDreamOverlay.aidl
@@ -16,8 +16,7 @@
package android.service.dreams;
-import android.service.dreams.IDreamOverlayCallback;
-import android.view.WindowManager.LayoutParams;
+import android.service.dreams.IDreamOverlayClientCallback;
/**
* {@link IDreamOverlay} provides a way for a component to annotate a dream with additional view
@@ -28,20 +27,7 @@
*/
interface IDreamOverlay {
/**
- * @param params The {@link LayoutParams} for the associated DreamWindow, including the window
- token of the Dream Activity.
- * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
- * dream.
- * @param dreamComponent The component name of the dream service requesting overlay.
- * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
- * and weather.
+ * Retrieves a client the caller can use to interact with the dream overlay.
*/
- void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
- in String dreamComponent, in boolean shouldShowComplications);
-
- /** Called when the dream is waking, to do any exit animations */
- void wakeUp();
-
- /** Called when the dream has ended. */
- void endDream();
+ void getClient(in IDreamOverlayClientCallback callback);
}
diff --git a/core/java/android/service/dreams/IDreamOverlayClient.aidl b/core/java/android/service/dreams/IDreamOverlayClient.aidl
new file mode 100644
index 0000000..78b7280
--- /dev/null
+++ b/core/java/android/service/dreams/IDreamOverlayClient.aidl
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.dreams;
+
+import android.service.dreams.IDreamOverlayCallback;
+import android.view.WindowManager.LayoutParams;
+
+/**
+* {@link IDreamOverlayClient} allows {@link DreamService} instances to act upon the dream overlay.
+*
+* @hide
+*/
+interface IDreamOverlayClient {
+ /**
+ * @param params The {@link LayoutParams} for the associated DreamWindow, including the window
+ token of the Dream Activity.
+ * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
+ * dream.
+ * @param dreamComponent The component name of the dream service requesting overlay.
+ * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
+ * and weather.
+ */
+ void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
+ in String dreamComponent, in boolean shouldShowComplications);
+
+ /** Called when the dream is waking, to do any exit animations */
+ void wakeUp();
+
+ /** Called when the dream has ended. */
+ void endDream();
+}
diff --git a/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl b/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl
new file mode 100644
index 0000000..244d999
--- /dev/null
+++ b/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.dreams;
+
+import android.service.dreams.IDreamOverlayClient;
+
+/**
+* {@link IDreamOverlayClientCallback} allows receiving a requested {@link IDreamOverlayClient}.
+* @hide
+*/
+interface IDreamOverlayClientCallback {
+ /**
+ * Called with a unique {@link IDreamOverlayClient}.
+ */
+ void onDreamOverlayClient(in IDreamOverlayClient client);
+}
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index b2dbfd0..4a7a235 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Prikazuje se ceo ekran"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Da biste izašli, prevucite nadole odozgo."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Važi"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotirajte radi boljeg prikaza"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Izađite iz podeljenog ekrana radi boljeg prikaza"</string>
<string name="done_label" msgid="7283767013231718521">"Gotovo"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Kružni klizač za sate"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Kružni klizač za minute"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index c8d6ca5..7d455c7 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotate for a better view"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Exit split screen for a better view"</string>
<string name="done_label" msgid="7283767013231718521">"Done"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Hours circular slider"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Minutes circular slider"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 601c498..b8767b7b 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotate for a better view"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Exit split screen for a better view"</string>
<string name="done_label" msgid="7283767013231718521">"Done"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Hours circular slider"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Minutes circular slider"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index df9cc2e..5f27e36 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Koko ruudun tilassa"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Sulje palkki pyyhkäisemällä alas ruudun ylälaidasta."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Selvä"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Kierrä, niin saat paremman näkymän"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Poistu jaetulta näytöltä, niin saat paremman näkymän"</string>
<string name="done_label" msgid="7283767013231718521">"Valmis"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Tuntien ympyränmuotoinen liukusäädin"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Minuuttien ympyränmuotoinen liukusäädin"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index cfc6418..8f5e7fb 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1842,10 +1842,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Peržiūrima viso ekrano režimu"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Jei norite išeiti, perbraukite žemyn iš viršaus."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Supratau"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Pasukite, kad geriau matytumėte vaizdą"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Išeikite iš išskaidyto ekrano režimo, kad geriau matytumėte vaizdą"</string>
<string name="done_label" msgid="7283767013231718521">"Atlikta"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Apskritas valandų šliaužiklis"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Apskritas minučių šliaužiklis"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 1c86c70..b8f017e 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -1077,13 +1077,13 @@
<string name="menu_space_shortcut_label" msgid="5949311515646872071">"ସ୍ପେସ୍"</string>
<string name="menu_enter_shortcut_label" msgid="6709499510082897320">"ଏଣ୍ଟର୍"</string>
<string name="menu_delete_shortcut_label" msgid="4365787714477739080">"ଡିଲିଟ୍ କରନ୍ତୁ"</string>
- <string name="search_go" msgid="2141477624421347086">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
- <string name="search_hint" msgid="455364685740251925">"ସନ୍ଧାନ…"</string>
- <string name="searchview_description_search" msgid="1045552007537359343">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
+ <string name="search_go" msgid="2141477624421347086">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+ <string name="search_hint" msgid="455364685740251925">"ସର୍ଚ୍ଚ କରନ୍ତୁ…"</string>
+ <string name="searchview_description_search" msgid="1045552007537359343">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
<string name="searchview_description_query" msgid="7430242366971716338">"କ୍ୱେରୀ ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
<string name="searchview_description_clear" msgid="1989371719192982900">"କ୍ୱେରୀ ଖାଲି କରନ୍ତୁ"</string>
<string name="searchview_description_submit" msgid="6771060386117334686">"କ୍ୱେରୀ ଦାଖଲ କରନ୍ତୁ"</string>
- <string name="searchview_description_voice" msgid="42360159504884679">"ଭଏସ୍ ସର୍ଚ୍ଚ"</string>
+ <string name="searchview_description_voice" msgid="42360159504884679">"ଭଏସ ସର୍ଚ୍ଚ"</string>
<string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍ କରନ୍ତୁ’ ସକ୍ଷମ କରିବେ?"</string>
<string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> ’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍ କରନ୍ତୁ’ ସକ୍ଷମ କରିବାକୁ ଚାହେଁ। ’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍ କରନ୍ତୁ’ ଅନ୍ ଥିବାବେଳେ, ଆପଣଙ୍କ ଆଙ୍ଗୁଠି ତଳେ କ’ଣ ଅଛି, ତାହାର ବ୍ୟାଖ୍ୟା ଦେଖିପାରିବେ କିମ୍ବା ଟାବ୍ଲେଟ୍ ସହ କଥାବାର୍ତ୍ତା କରିବାକୁ ଜେଶ୍ଚର୍ କରିପାରିବେ।"</string>
<string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> ’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍ କରନ୍ତୁ’ ସକ୍ଷମ କରିବାକୁ ଚାହେଁ। ’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍ କରନ୍ତୁ’ ଅନ୍ ଥିବାବେଳେ, ଆପଣଙ୍କ ଆଙ୍ଗୁଠି ତଳେ କ’ଣ ଅଛି, ତାହାର ବ୍ୟାଖ୍ୟା ଦେଖିପାରିବେ କିମ୍ବା ଫୋନ୍ ସହ କଥାବାର୍ତ୍ତା କରିବାକୁ ଜେଶ୍ଚର୍ କରିପାରିବେ।"</string>
@@ -1463,7 +1463,7 @@
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ଜୁମ୍ ନିୟନ୍ତ୍ରଣ ପାଇଁ ଦୁଇଥର ଟାପ୍ କରନ୍ତୁ"</string>
<string name="gadget_host_error_inflating" msgid="2449961590495198720">"ୱିଜେଟ୍ ଯୋଡ଼ିପାରିବ ନାହିଁ।"</string>
<string name="ime_action_go" msgid="5536744546326495436">"ଯାଆନ୍ତୁ"</string>
- <string name="ime_action_search" msgid="4501435960587287668">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
+ <string name="ime_action_search" msgid="4501435960587287668">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
<string name="ime_action_send" msgid="8456843745664334138">"ପଠାନ୍ତୁ"</string>
<string name="ime_action_next" msgid="4169702997635728543">"ପରବର୍ତ୍ତୀ"</string>
<string name="ime_action_done" msgid="6299921014822891569">"ହୋଇଗଲା"</string>
@@ -1935,7 +1935,7 @@
<string name="language_picker_regions_section_suggested" msgid="6080131515268225316">"ପ୍ରସ୍ତାବିତ"</string>
<string name="language_picker_section_all" msgid="1985809075777564284">"ସମସ୍ତ ଭାଷା"</string>
<string name="region_picker_section_all" msgid="756441309928774155">"ସମସ୍ତ ଅଞ୍ଚଳ"</string>
- <string name="locale_search_menu" msgid="6258090710176422934">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
+ <string name="locale_search_menu" msgid="6258090710176422934">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
<string name="app_suspended_title" msgid="888873445010322650">"ଆପ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
<string name="app_suspended_default_message" msgid="6451215678552004172">"ବର୍ତ୍ତମାନ <xliff:g id="APP_NAME_0">%1$s</xliff:g> ଉପଲବ୍ଧ ନାହିଁ। ଏହା <xliff:g id="APP_NAME_1">%2$s</xliff:g> ଦ୍ଵାରା ପରିଚାଳିତ ହେଉଛି।"</string>
<string name="app_suspended_more_details" msgid="211260942831587014">"ଅଧିକ ଜାଣନ୍ତୁ"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index f3c758c..bf8c0f7 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Приказује се цео екран"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Да бисте изашли, превуците надоле одозго."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Важи"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Ротирајте ради бољег приказа"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Изађите из подељеног екрана ради бољег приказа"</string>
<string name="done_label" msgid="7283767013231718521">"Готово"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Кружни клизач за сате"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Кружни клизач за минуте"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index de858e7..8d3bedf 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Butun ekranli rejim"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Chiqish uchun tepadan pastga torting."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Yaxshiroq koʻrish uchun kamerani buring"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Yaxshiroq koʻrish uchun ajratilgan ekran rejimidan chiqing"</string>
<string name="done_label" msgid="7283767013231718521">"Tayyor"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Doiradan soatni tanlang"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Doiradan daqiqani tanlang"</string>
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index f615ad6..0f45219 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -47,6 +47,7 @@
"src/com/android/wm/shell/sysui/ShellSharedConstants.java",
"src/com/android/wm/shell/common/TransactionPool.java",
"src/com/android/wm/shell/animation/Interpolators.java",
+ "src/com/android/wm/shell/pip/PipContentOverlay.java",
"src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
],
path: "src",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 7096a64..283b1ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
@@ -41,6 +42,11 @@
}
}
+ @Nullable
+ public SurfaceControl getLeash() {
+ return mLeash;
+ }
+
/**
* Animates the internal {@link #mLeash} by a given fraction.
* @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index f77ac81..476a7ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -174,7 +174,7 @@
final CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
- windowDecoration.setDragResizeCallback(taskPositioner);
+ windowDecoration.setDragPositioningCallback(taskPositioner);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT);
setupCaptionColor(taskInfo, windowDecoration);
@@ -185,17 +185,17 @@
private final int mTaskId;
private final WindowContainerToken mTaskToken;
- private final DragResizeCallback mDragResizeCallback;
+ private final DragPositioningCallback mDragPositioningCallback;
private final DragDetector mDragDetector;
private int mDragPointerId = -1;
private CaptionTouchEventListener(
RunningTaskInfo taskInfo,
- DragResizeCallback dragResizeCallback) {
+ DragPositioningCallback dragPositioningCallback) {
mTaskId = taskInfo.taskId;
mTaskToken = taskInfo.token;
- mDragResizeCallback = dragResizeCallback;
+ mDragPositioningCallback = dragPositioningCallback;
mDragDetector = new DragDetector(this);
}
@@ -247,20 +247,20 @@
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mDragPointerId = e.getPointerId(0);
- mDragResizeCallback.onDragResizeStart(
+ mDragPositioningCallback.onDragPositioningStart(
0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
break;
}
case MotionEvent.ACTION_MOVE: {
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- mDragResizeCallback.onDragResizeMove(
+ mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- mDragResizeCallback.onDragResizeEnd(
+ mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
break;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index f94fbfc..060dc4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -47,7 +47,7 @@
private View.OnClickListener mOnCaptionButtonClickListener;
private View.OnTouchListener mOnCaptionTouchListener;
- private DragResizeCallback mDragResizeCallback;
+ private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
@@ -78,8 +78,8 @@
mOnCaptionTouchListener = onCaptionTouchListener;
}
- void setDragResizeCallback(DragResizeCallback dragResizeCallback) {
- mDragResizeCallback = dragResizeCallback;
+ void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) {
+ mDragPositioningCallback = dragPositioningCallback;
}
void setDragDetector(DragDetector dragDetector) {
@@ -151,7 +151,7 @@
mChoreographer,
mDisplay.getDisplayId(),
mDecorationContainerSurface,
- mDragResizeCallback);
+ mDragPositioningCallback);
}
final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index c0c0ab9..606cf28 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -209,17 +209,17 @@
private final int mTaskId;
private final WindowContainerToken mTaskToken;
- private final DragResizeCallback mDragResizeCallback;
+ private final DragPositioningCallback mDragPositioningCallback;
private final DragDetector mDragDetector;
private int mDragPointerId = -1;
private DesktopModeTouchEventListener(
RunningTaskInfo taskInfo,
- DragResizeCallback dragResizeCallback) {
+ DragPositioningCallback dragPositioningCallback) {
mTaskId = taskInfo.taskId;
mTaskToken = taskInfo.token;
- mDragResizeCallback = dragResizeCallback;
+ mDragPositioningCallback = dragPositioningCallback;
mDragDetector = new DragDetector(this);
}
@@ -283,13 +283,13 @@
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mDragPointerId = e.getPointerId(0);
- mDragResizeCallback.onDragResizeStart(
+ mDragPositioningCallback.onDragPositioningStart(
0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
break;
}
case MotionEvent.ACTION_MOVE: {
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- mDragResizeCallback.onDragResizeMove(
+ mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
break;
}
@@ -298,7 +298,7 @@
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
final int statusBarHeight = mDisplayController
.getDisplayLayout(taskInfo.displayId).stableInsets().top;
- mDragResizeCallback.onDragResizeEnd(
+ mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
if (DesktopModeStatus.isProto2Enabled()) {
@@ -557,7 +557,7 @@
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
- windowDecoration.setDragResizeCallback(taskPositioner);
+ windowDecoration.setDragPositioningCallback(taskPositioner);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT);
incrementEventReceiverTasks(taskInfo.displayId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 31b56d3..744c18f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -54,7 +54,7 @@
private View.OnClickListener mOnCaptionButtonClickListener;
private View.OnTouchListener mOnCaptionTouchListener;
- private DragResizeCallback mDragResizeCallback;
+ private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
@@ -90,8 +90,8 @@
mOnCaptionTouchListener = onCaptionTouchListener;
}
- void setDragResizeCallback(DragResizeCallback dragResizeCallback) {
- mDragResizeCallback = dragResizeCallback;
+ void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) {
+ mDragPositioningCallback = dragPositioningCallback;
}
void setDragDetector(DragDetector dragDetector) {
@@ -179,7 +179,7 @@
mChoreographer,
mDisplay.getDisplayId(),
mDecorationContainerSurface,
- mDragResizeCallback);
+ mDragPositioningCallback);
}
final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
similarity index 76%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index ee160a1..0191c60 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -19,28 +19,28 @@
/**
* Callback called when receiving drag-resize or drag-move related input events.
*/
-public interface DragResizeCallback {
+public interface DragPositioningCallback {
/**
- * Called when a drag resize starts.
+ * Called when a drag-resize or drag-move starts.
*
* @param ctrlType {@link TaskPositioner.CtrlType} indicating the direction of resizing, use
* {@code 0} to indicate it's a move
- * @param x x coordinate in window decoration coordinate system where the drag resize starts
- * @param y y coordinate in window decoration coordinate system where the drag resize starts
+ * @param x x coordinate in window decoration coordinate system where the drag starts
+ * @param y y coordinate in window decoration coordinate system where the drag starts
*/
- void onDragResizeStart(@TaskPositioner.CtrlType int ctrlType, float x, float y);
+ void onDragPositioningStart(@TaskPositioner.CtrlType int ctrlType, float x, float y);
/**
- * Called when the pointer moves during a drag resize.
+ * Called when the pointer moves during a drag-resize or drag-move.
* @param x x coordinate in window decoration coordinate system of the new pointer location
* @param y y coordinate in window decoration coordinate system of the new pointer location
*/
- void onDragResizeMove(float x, float y);
+ void onDragPositioningMove(float x, float y);
/**
- * Called when a drag resize stops.
+ * Called when a drag-resize or drag-move stops.
* @param x x coordinate in window decoration coordinate system where the drag resize stops
* @param y y coordinate in window decoration coordinate system where the drag resize stops
*/
- void onDragResizeEnd(float x, float y);
+ void onDragPositioningEnd(float x, float y);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 2963763..7d954ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -62,7 +62,7 @@
private final SurfaceControl mDecorationSurface;
private final InputChannel mInputChannel;
private final TaskResizeInputEventReceiver mInputEventReceiver;
- private final com.android.wm.shell.windowdecor.DragResizeCallback mCallback;
+ private final DragPositioningCallback mCallback;
private int mWidth;
private int mHeight;
@@ -83,7 +83,7 @@
Choreographer choreographer,
int displayId,
SurfaceControl decorationSurface,
- DragResizeCallback callback) {
+ DragPositioningCallback callback) {
mInputManager = context.getSystemService(InputManager.class);
mHandler = handler;
mChoreographer = choreographer;
@@ -293,7 +293,7 @@
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
int ctrlType = calculateCtrlType(isTouch, x, y);
- mCallback.onDragResizeStart(ctrlType, rawX, rawY);
+ mCallback.onDragPositioningStart(ctrlType, rawX, rawY);
result = true;
}
break;
@@ -305,7 +305,7 @@
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
float rawX = e.getRawX(dragPointerIndex);
float rawY = e.getRawY(dragPointerIndex);
- mCallback.onDragResizeMove(rawX, rawY);
+ mCallback.onDragPositioningMove(rawX, rawY);
result = true;
break;
}
@@ -313,7 +313,7 @@
case MotionEvent.ACTION_CANCEL: {
if (mShouldHandleEvents) {
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
- mCallback.onDragResizeEnd(
+ mCallback.onDragPositioningEnd(
e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
}
mShouldHandleEvents = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index 8cd2a59..d3f9227 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -23,7 +23,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
-class TaskPositioner implements DragResizeCallback {
+class TaskPositioner implements DragPositioningCallback {
@IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
@interface CtrlType {}
@@ -38,8 +38,8 @@
private final WindowDecoration mWindowDecoration;
private final Rect mTaskBoundsAtDragStart = new Rect();
- private final PointF mResizeStartPoint = new PointF();
- private final Rect mResizeTaskBounds = new Rect();
+ private final PointF mRepositionStartPoint = new PointF();
+ private final Rect mRepositionTaskBounds = new Rect();
private boolean mHasMoved = false;
private int mCtrlType;
@@ -57,7 +57,7 @@
}
@Override
- public void onDragResizeStart(int ctrlType, float x, float y) {
+ public void onDragPositioningStart(int ctrlType, float x, float y) {
mHasMoved = false;
mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
@@ -65,11 +65,11 @@
mTaskBoundsAtDragStart.set(
mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
- mResizeStartPoint.set(x, y);
+ mRepositionStartPoint.set(x, y);
}
@Override
- public void onDragResizeMove(float x, float y) {
+ public void onDragPositioningMove(float x, float y) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (changeBounds(wct, x, y)) {
// The task is being resized, send the |dragResizing| hint to core with the first
@@ -84,7 +84,7 @@
}
@Override
- public void onDragResizeEnd(float x, float y) {
+ public void onDragPositioningEnd(float x, float y) {
// |mHasMoved| being false means there is no real change to the task bounds in WM core, so
// we don't need a WCT to finish it.
if (mHasMoved) {
@@ -96,42 +96,44 @@
mCtrlType = CTRL_TYPE_UNDEFINED;
mTaskBoundsAtDragStart.setEmpty();
- mResizeStartPoint.set(0, 0);
+ mRepositionStartPoint.set(0, 0);
mHasMoved = false;
}
private boolean changeBounds(WindowContainerTransaction wct, float x, float y) {
- // |mResizeTaskBounds| is the bounds last reported if |mHasMoved| is true. If it's not true,
- // we can compare it against |mTaskBoundsAtDragStart|.
- final int oldLeft = mHasMoved ? mResizeTaskBounds.left : mTaskBoundsAtDragStart.left;
- final int oldTop = mHasMoved ? mResizeTaskBounds.top : mTaskBoundsAtDragStart.top;
- final int oldRight = mHasMoved ? mResizeTaskBounds.right : mTaskBoundsAtDragStart.right;
- final int oldBottom = mHasMoved ? mResizeTaskBounds.bottom : mTaskBoundsAtDragStart.bottom;
+ // |mRepositionTaskBounds| is the bounds last reported if |mHasMoved| is true. If it's not
+ // true, we can compare it against |mTaskBoundsAtDragStart|.
+ final int oldLeft = mHasMoved ? mRepositionTaskBounds.left : mTaskBoundsAtDragStart.left;
+ final int oldTop = mHasMoved ? mRepositionTaskBounds.top : mTaskBoundsAtDragStart.top;
+ final int oldRight = mHasMoved ? mRepositionTaskBounds.right : mTaskBoundsAtDragStart.right;
+ final int oldBottom =
+ mHasMoved ? mRepositionTaskBounds.bottom : mTaskBoundsAtDragStart.bottom;
- final float deltaX = x - mResizeStartPoint.x;
- final float deltaY = y - mResizeStartPoint.y;
- mResizeTaskBounds.set(mTaskBoundsAtDragStart);
+ final float deltaX = x - mRepositionStartPoint.x;
+ final float deltaY = y - mRepositionStartPoint.y;
+ mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
- mResizeTaskBounds.left += deltaX;
+ mRepositionTaskBounds.left += deltaX;
}
if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) {
- mResizeTaskBounds.right += deltaX;
+ mRepositionTaskBounds.right += deltaX;
}
if ((mCtrlType & CTRL_TYPE_TOP) != 0) {
- mResizeTaskBounds.top += deltaY;
+ mRepositionTaskBounds.top += deltaY;
}
if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) {
- mResizeTaskBounds.bottom += deltaY;
+ mRepositionTaskBounds.bottom += deltaY;
}
if (mCtrlType == CTRL_TYPE_UNDEFINED) {
- mResizeTaskBounds.offset((int) deltaX, (int) deltaY);
+ mRepositionTaskBounds.offset((int) deltaX, (int) deltaY);
}
- if (oldLeft == mResizeTaskBounds.left && oldTop == mResizeTaskBounds.top
- && oldRight == mResizeTaskBounds.right && oldBottom == mResizeTaskBounds.bottom) {
+ if (oldLeft == mRepositionTaskBounds.left && oldTop == mRepositionTaskBounds.top
+ && oldRight == mRepositionTaskBounds.right
+ && oldBottom == mRepositionTaskBounds.bottom) {
return false;
}
- wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
+ wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
return true;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
index 804c416..f185a8a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -66,13 +66,13 @@
@Test
fun testDragResize_notMove_skipsTransactionOnEnd() {
- taskPositioner.onDragResizeStart(
+ taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- taskPositioner.onDragResizeEnd(
+ taskPositioner.onDragPositioningEnd(
STARTING_BOUNDS.left.toFloat() + 10,
STARTING_BOUNDS.top.toFloat() + 10
)
@@ -87,18 +87,18 @@
@Test
fun testDragResize_noEffectiveMove_skipsTransactionOnMoveAndEnd() {
- taskPositioner.onDragResizeStart(
+ taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- taskPositioner.onDragResizeMove(
+ taskPositioner.onDragPositioningMove(
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- taskPositioner.onDragResizeEnd(
+ taskPositioner.onDragPositioningEnd(
STARTING_BOUNDS.left.toFloat() + 10,
STARTING_BOUNDS.top.toFloat() + 10
)
@@ -113,13 +113,13 @@
@Test
fun testDragResize_hasEffectiveMove_issuesTransactionOnMoveAndEnd() {
- taskPositioner.onDragResizeStart(
+ taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- taskPositioner.onDragResizeMove(
+ taskPositioner.onDragPositioningMove(
STARTING_BOUNDS.left.toFloat() + 10,
STARTING_BOUNDS.top.toFloat()
)
@@ -133,7 +133,7 @@
}
})
- taskPositioner.onDragResizeEnd(
+ taskPositioner.onDragPositioningEnd(
STARTING_BOUNDS.left.toFloat() + 10,
STARTING_BOUNDS.top.toFloat() + 10
)
@@ -150,7 +150,7 @@
@Test
fun testDragResize_move_skipsDragResizingFlag() {
- taskPositioner.onDragResizeStart(
+ taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // Move
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
@@ -159,12 +159,12 @@
// Move the task 10px to the right.
val newX = STARTING_BOUNDS.left.toFloat() + 10
val newY = STARTING_BOUNDS.top.toFloat()
- taskPositioner.onDragResizeMove(
+ taskPositioner.onDragPositioningMove(
newX,
newY
)
- taskPositioner.onDragResizeEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(newX, newY)
verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
@@ -177,7 +177,7 @@
@Test
fun testDragResize_resize_setsDragResizingFlag() {
- taskPositioner.onDragResizeStart(
+ taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
@@ -186,12 +186,12 @@
// Resize the task by 10px to the right.
val newX = STARTING_BOUNDS.right.toFloat() + 10
val newY = STARTING_BOUNDS.top.toFloat()
- taskPositioner.onDragResizeMove(
+ taskPositioner.onDragPositioningMove(
newX,
newY
)
- taskPositioner.onDragResizeEnd(newX, newY)
+ taskPositioner.onDragPositioningEnd(newX, newY)
verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
diff --git a/packages/PrintSpooler/res/values-or/strings.xml b/packages/PrintSpooler/res/values-or/strings.xml
index 6f215d3..dd29700 100644
--- a/packages/PrintSpooler/res/values-or/strings.xml
+++ b/packages/PrintSpooler/res/values-or/strings.xml
@@ -47,7 +47,7 @@
<string name="savetopdf_button" msgid="2976186791686924743">"PDFରେ ସେଭ୍ କରନ୍ତୁ"</string>
<string name="print_options_expanded" msgid="6944679157471691859">"ପ୍ରିଣ୍ଟ ବିକଳ୍ପକୁ ବଡ଼ କରାଯାଇଛି"</string>
<string name="print_options_collapsed" msgid="7455930445670414332">"ପ୍ରିଣ୍ଟ ବିକଳ୍ପକୁ ଛୋଟ କରାଯାଇଛି"</string>
- <string name="search" msgid="5421724265322228497">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
+ <string name="search" msgid="5421724265322228497">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
<string name="all_printers_label" msgid="3178848870161526399">"ସମସ୍ତ ପ୍ରିଣ୍ଟର୍"</string>
<string name="add_print_service_label" msgid="5356702546188981940">"ସେବା ଯୋଗ କରନ୍ତୁ"</string>
<string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ସର୍ଚ୍ଚ ବକ୍ସ ଦେଖାଯାଇଛି"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index d6586db..d9d7cc9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -185,6 +185,24 @@
public abstract String getId();
/**
+ * Get disabled reason of device
+ *
+ * @return disabled reason of device
+ */
+ public int getDisableReason() {
+ return -1;
+ }
+
+ /**
+ * Checks if device is has disabled reason
+ *
+ * @return true if device has disabled reason
+ */
+ public boolean hasDisabledReason() {
+ return false;
+ }
+
+ /**
* Checks if device is suggested device from application
*
* @return true if device is suggested device
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index c729b09..17a94b86 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -41,6 +41,7 @@
import androidx.annotation.UiThread
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
+import java.lang.IllegalArgumentException
import kotlin.math.roundToInt
private const val TAG = "ActivityLaunchAnimator"
@@ -338,13 +339,24 @@
* Return a [Controller] that will animate and expand [view] into the opening window.
*
* Important: The view must be attached to a [ViewGroup] when calling this function and
- * during the animation. For safety, this method will return null when it is not.
+ * during the animation. For safety, this method will return null when it is not. The
+ * view must also implement [LaunchableView], otherwise this method will throw.
*
* Note: The background of [view] should be a (rounded) rectangle so that it can be
* properly animated.
*/
@JvmStatic
fun fromView(view: View, cujType: Int? = null): Controller? {
+ // Make sure the View we launch from implements LaunchableView to avoid visibility
+ // issues.
+ if (view !is LaunchableView) {
+ throw IllegalArgumentException(
+ "An ActivityLaunchAnimator.Controller was created from a View that does " +
+ "not implement LaunchableView. This can lead to subtle bugs where the" +
+ " visibility of the View we are launching from is not what we expected."
+ )
+ }
+
if (view.parent !is ViewGroup) {
Log.e(
TAG,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index e91a671..b8d78fb 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -40,6 +40,7 @@
import com.android.systemui.animation.back.applyTo
import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
import com.android.systemui.animation.back.onBackAnimationCallbackFrom
+import java.lang.IllegalArgumentException
import kotlin.math.roundToInt
private const val TAG = "DialogLaunchAnimator"
@@ -157,12 +158,23 @@
* Create a [Controller] that can animate [source] to and from a dialog.
*
* Important: The view must be attached to a [ViewGroup] when calling this function and
- * during the animation. For safety, this method will return null when it is not.
+ * during the animation. For safety, this method will return null when it is not. The
+ * view must also implement [LaunchableView], otherwise this method will throw.
*
* Note: The background of [view] should be a (rounded) rectangle so that it can be
* properly animated.
*/
fun fromView(source: View, cuj: DialogCuj? = null): Controller? {
+ // Make sure the View we launch from implements LaunchableView to avoid visibility
+ // issues.
+ if (source !is LaunchableView) {
+ throw IllegalArgumentException(
+ "A DialogLaunchAnimator.Controller was created from a View that does not " +
+ "implement LaunchableView. This can lead to subtle bugs where the " +
+ "visibility of the View we are launching from is not what we expected."
+ )
+ }
+
if (source.parent !is ViewGroup) {
Log.e(
TAG,
@@ -249,23 +261,6 @@
}
?: controller
- if (
- animatedParent == null &&
- controller is ViewDialogLaunchAnimatorController &&
- controller.source !is LaunchableView
- ) {
- // Make sure the View we launch from implements LaunchableView to avoid visibility
- // issues. Given that we don't own dialog decorViews so we can't enforce it for launches
- // from a dialog.
- // TODO(b/243636422): Throw instead of logging to enforce this.
- Log.w(
- TAG,
- "A dialog was launched from a View that does not implement LaunchableView. This " +
- "can lead to subtle bugs where the visibility of the View we are " +
- "launching from is not what we expected."
- )
- }
-
// Make sure we don't run the launch animation from the same source twice at the same time.
if (openedDialogs.any { it.controller.sourceIdentity == controller.sourceIdentity }) {
Log.e(
@@ -613,10 +608,16 @@
}
// Animate that view with the background. Throw if we didn't find one, because
- // otherwise
- // it's not clear what we should animate.
+ // otherwise it's not clear what we should animate.
+ if (viewGroupWithBackground == null) {
+ error("Unable to find ViewGroup with background")
+ }
+
+ if (viewGroupWithBackground !is LaunchableView) {
+ error("The animated ViewGroup with background must implement LaunchableView")
+ }
+
viewGroupWithBackground
- ?: throw IllegalStateException("Unable to find ViewGroup with background")
} else {
// We will make the dialog window (and therefore its DecorView) fullscreen to make
// it possible to animate outside its bounds.
@@ -639,7 +640,7 @@
FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
)
- val dialogContentWithBackground = FrameLayout(dialog.context)
+ val dialogContentWithBackground = LaunchableFrameLayout(dialog.context)
dialogContentWithBackground.background = decorView.background
// Make the window background transparent. Note that setting the window (or
@@ -720,7 +721,10 @@
// Make the background view invisible until we start the animation. We use the transition
// visibility like GhostView does so that we don't mess up with the accessibility tree (see
- // b/204944038#comment17).
+ // b/204944038#comment17). Given that this background implements LaunchableView, we call
+ // setShouldBlockVisibilityChanges() early so that the current visibility (VISIBLE) is
+ // restored at the end of the animation.
+ dialogContentWithBackground.setShouldBlockVisibilityChanges(true)
dialogContentWithBackground.setTransitionVisibility(View.INVISIBLE)
// Make sure the dialog is visible instantly and does not do any window animation.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 26aa0e8..23e3a01 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -34,6 +34,7 @@
import android.view.ViewGroupOverlay
import android.widget.FrameLayout
import com.android.internal.jank.InteractionJankMonitor
+import java.lang.IllegalArgumentException
import java.util.LinkedList
import kotlin.math.min
import kotlin.math.roundToInt
@@ -46,7 +47,8 @@
* of the ghosted view.
*
* Important: [ghostedView] must be attached to a [ViewGroup] when calling this function and during
- * the animation.
+ * the animation. It must also implement [LaunchableView], otherwise an exception will be thrown
+ * during this controller instantiation.
*
* Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
* whenever possible instead.
@@ -101,6 +103,15 @@
private val background: Drawable?
init {
+ // Make sure the View we launch from implements LaunchableView to avoid visibility issues.
+ if (ghostedView !is LaunchableView) {
+ throw IllegalArgumentException(
+ "A GhostedViewLaunchAnimatorController was created from a View that does not " +
+ "implement LaunchableView. This can lead to subtle bugs where the visibility " +
+ "of the View we are launching from is not what we expected."
+ )
+ }
+
/** Find the first view with a background in [view] and its children. */
fun findBackground(view: View): Drawable? {
if (view.background != null) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt
new file mode 100644
index 0000000..2eb503b
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.animation
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+
+/** A [FrameLayout] that also implements [LaunchableView]. */
+open class LaunchableFrameLayout : FrameLayout, LaunchableView {
+ private val delegate =
+ LaunchableViewDelegate(
+ this,
+ superSetVisibility = { super.setVisibility(it) },
+ )
+
+ constructor(context: Context) : super(context)
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int
+ ) : super(context, attrs, defStyleAttr)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ defStyleRes: Int
+ ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {
+ delegate.setShouldBlockVisibilityChanges(block)
+ }
+
+ override fun setVisibility(visibility: Int) {
+ delegate.setVisibility(visibility)
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index 9257f99..46d5a5c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -25,7 +25,7 @@
/** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */
class ViewDialogLaunchAnimatorController
internal constructor(
- internal val source: View,
+ private val source: View,
override val cuj: DialogCuj?,
) : DialogLaunchAnimator.Controller {
override val viewRoot: ViewRootImpl?
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
index 0e3d41c..7897934 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
@@ -48,7 +48,7 @@
animator.addUpdateListener { updateListener ->
val now = updateListener.currentPlayTime
val progress = updateListener.animatedValue as Float
- rippleShader.progress = progress
+ rippleShader.rawProgress = progress
rippleShader.distortionStrength = if (config.shouldDistort) 1 - progress else 0f
rippleShader.time = now.toFloat()
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index 9058510..4322d53 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -18,6 +18,7 @@
import android.graphics.PointF
import android.graphics.RuntimeShader
import android.util.MathUtils
+import com.android.systemui.animation.Interpolators
import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
@@ -47,7 +48,6 @@
"""
uniform vec2 in_center;
uniform vec2 in_size;
- uniform float in_progress;
uniform float in_cornerRadius;
uniform float in_thickness;
uniform float in_time;
@@ -162,21 +162,15 @@
maxSize.y = height
}
- /** Progress of the ripple. Float value between [0, 1]. */
- var progress: Float = 0.0f
+ /**
+ * Linear progress of the ripple. Float value between [0, 1].
+ *
+ * <p>Note that the progress here is expected to be linear without any curve applied.
+ */
+ var rawProgress: Float = 0.0f
set(value) {
field = value
- setFloatUniform("in_progress", value)
- val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
-
- currentWidth = maxSize.x * curvedProg
- currentHeight = maxSize.y * curvedProg
- setFloatUniform("in_size", /* width= */ currentWidth, /* height= */ currentHeight)
- 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))
+ progress = Interpolators.STANDARD.getInterpolation(value)
val fadeIn = subProgress(0f, 0.1f, value)
val fadeOutNoise = subProgress(0.4f, 1f, value)
@@ -191,6 +185,20 @@
setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
}
+ /** Progress with Standard easing curve applied. */
+ private var progress: Float = 0.0f
+ set(value) {
+ currentWidth = maxSize.x * value
+ currentHeight = maxSize.y * value
+ setFloatUniform("in_size", currentWidth, currentHeight)
+
+ setFloatUniform("in_thickness", maxSize.y * value * 0.5f)
+ // radius should not exceed width and height values.
+ setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * value)
+
+ setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
+ }
+
/** Play time since the start of the effect. */
var time: Float = 0.0f
set(value) {
@@ -220,7 +228,7 @@
var distortionStrength: Float = 0.0f
set(value) {
field = value
- setFloatUniform("in_distort_radial", 75 * progress * value)
+ setFloatUniform("in_distort_radial", 75 * rawProgress * value)
setFloatUniform("in_distort_xy", 75 * value)
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index b37c734..bc4796a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -77,7 +77,7 @@
rippleShader = RippleShader(rippleShape)
rippleShader.color = RippleAnimationConfig.RIPPLE_DEFAULT_COLOR
- rippleShader.progress = 0f
+ rippleShader.rawProgress = 0f
rippleShader.sparkleStrength = RippleAnimationConfig.RIPPLE_SPARKLE_STRENGTH
rippleShader.pixelDensity = resources.displayMetrics.density
@@ -93,7 +93,7 @@
animator.addUpdateListener { updateListener ->
val now = updateListener.currentPlayTime
val progress = updateListener.animatedValue as Float
- rippleShader.progress = progress
+ rippleShader.rawProgress = progress
rippleShader.distortionStrength = 1 - progress
rippleShader.time = now.toFloat()
invalidate()
@@ -142,25 +142,13 @@
}
// 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.
+ // in the ripple shader. (Twice bigger)
if (rippleShape == RippleShape.CIRCLE) {
- val maskRadius =
- (1 -
- (1 - rippleShader.progress) *
- (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * maxWidth
+ val maskRadius = rippleShader.currentWidth
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
+ val maskWidth = rippleShader.currentWidth * 2
+ val maskHeight = rippleShader.currentHeight * 2
canvas.drawRect(
/* left= */ centerX - maskWidth,
/* top= */ centerY - maskHeight,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index 4e96dda..cfc38df 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -33,8 +33,8 @@
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalContentColor
-import androidx.compose.material3.LocalMinimumTouchTargetEnforcement
import androidx.compose.material3.contentColorFor
+import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
@@ -65,21 +65,17 @@
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.findRootCoordinates
-import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
-import com.android.compose.runtime.movableContentOf
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.LaunchAnimator
import kotlin.math.max
import kotlin.math.min
-import kotlin.math.roundToInt
/**
* Create an expandable shape that can launch into an Activity or a Dialog.
@@ -220,21 +216,8 @@
// If this expandable is expanded when it's being directly clicked on, let's ensure that it has
// the minimum interactive size followed by all M3 components (48.dp).
val minInteractiveSizeModifier =
- if (onClick != null && LocalMinimumTouchTargetEnforcement.current) {
- // TODO(b/242040009): Replace this by Modifier.minimumInteractiveComponentSize() once
- // http://aosp/2305511 is available.
- val minTouchSize = LocalViewConfiguration.current.minimumTouchTargetSize
- Modifier.layout { measurable, constraints ->
- // Copied from androidx.compose.material3.InteractiveComponentSize.kt
- val placeable = measurable.measure(constraints)
- val width = maxOf(placeable.width, minTouchSize.width.roundToPx())
- val height = maxOf(placeable.height, minTouchSize.height.roundToPx())
- layout(width, height) {
- val centerX = ((width - placeable.width) / 2f).roundToInt()
- val centerY = ((height - placeable.height) / 2f).roundToInt()
- placeable.place(centerX, centerY)
- }
- }
+ if (onClick != null) {
+ Modifier.minimumInteractiveComponentSize()
} else {
Modifier
}
diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
index de96e97..446bb01 100644
--- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -20,7 +20,7 @@
android:layout_width="wrap_content"
android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding">
- <ImageView
+ <com.android.systemui.common.ui.view.LaunchableImageView
android:id="@+id/home_controls_chip"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
diff --git a/packages/SystemUI/res/layout/global_actions_grid_lite.xml b/packages/SystemUI/res/layout/global_actions_grid_lite.xml
index 5588fd3..a64c9ae 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_lite.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_lite.xml
@@ -33,7 +33,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_weight="1">
- <androidx.constraintlayout.widget.ConstraintLayout
+ <com.android.systemui.common.ui.view.LaunchableConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@android:id/list"
@@ -55,6 +55,6 @@
app:flow_horizontalGap="@dimen/global_actions_lite_padding"
app:flow_verticalGap="@dimen/global_actions_lite_padding"
app:flow_horizontalStyle="packed"/>
- </androidx.constraintlayout.widget.ConstraintLayout>
+ </com.android.systemui.common.ui.view.LaunchableConstraintLayout>
</com.android.systemui.globalactions.GlobalActionsLayoutLite>
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
index 6f33623..07c428b 100644
--- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
+++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
@@ -24,7 +24,7 @@
android:layout_gravity="end">
<!-- We add a background behind the UserAvatarView with the same color and with a circular shape
so that this view can be expanded into a Dialog or an Activity. -->
- <FrameLayout
+ <com.android.systemui.animation.LaunchableFrameLayout
android:id="@+id/kg_multi_user_avatar_with_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -42,5 +42,5 @@
systemui:framePadding="0dp"
systemui:frameWidth="0dp">
</com.android.systemui.statusbar.phone.UserAvatarView>
- </FrameLayout>
+ </com.android.systemui.animation.LaunchableFrameLayout>
</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_recommendation_view.xml b/packages/SystemUI/res/layout/media_recommendation_view.xml
new file mode 100644
index 0000000..101fad9
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_recommendation_view.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<!-- Layout for media recommendation item inside QSPanel carousel -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Album cover -->
+ <ImageView
+ android:id="@+id/media_cover"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:translationZ="0dp"
+ android:scaleType="centerCrop"
+ android:adjustViewBounds="true"
+ android:clipToOutline="true"
+ android:background="@drawable/bg_smartspace_media_item"/>
+
+ <!-- App icon -->
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/media_rec_app_icon"
+ android:layout_width="@dimen/qs_media_rec_icon_top_margin"
+ android:layout_height="@dimen/qs_media_rec_icon_top_margin"
+ android:layout_marginStart="@dimen/qs_media_info_spacing"
+ android:layout_marginTop="@dimen/qs_media_info_spacing"/>
+
+ <!-- Artist name -->
+ <TextView
+ android:id="@+id/media_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/qs_media_info_spacing"
+ android:layout_marginEnd="@dimen/qs_media_info_spacing"
+ android:layout_marginBottom="@dimen/qs_media_rec_album_title_bottom_margin"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:singleLine="true"
+ android:textSize="12sp"
+ android:gravity="top"
+ android:layout_gravity="bottom"/>
+
+ <!-- Album name -->
+ <TextView
+ android:id="@+id/media_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_rec_album_subtitle_height"
+ android:layout_marginEnd="@dimen/qs_media_info_spacing"
+ android:layout_marginStart="@dimen/qs_media_info_spacing"
+ android:layout_marginBottom="@dimen/qs_media_info_spacing"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
+ android:singleLine="true"
+ android:textSize="11sp"
+ android:gravity="center_vertical"
+ android:layout_gravity="bottom"/>
+</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_recommendations.xml b/packages/SystemUI/res/layout/media_recommendations.xml
new file mode 100644
index 0000000..65fc19c
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_recommendations.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<!-- Layout for media recommendations inside QSPanel carousel -->
+<com.android.systemui.util.animation.TransitionLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/media_recommendations_updated"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:forceHasOverlappingRendering="false"
+ android:background="@drawable/qs_media_background"
+ android:theme="@style/MediaPlayer">
+
+ <!-- This view just ensures the full media player is a certain height. -->
+ <View
+ android:id="@+id/sizing_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_expanded" />
+
+ <TextView
+ android:id="@+id/media_rec_title"
+ style="@style/MediaPlayer.Recommendation.Header"
+ android:text="@string/controls_media_smartspace_rec_header"/>
+
+ <FrameLayout
+ android:id="@+id/media_cover1_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ >
+
+ <include
+ layout="@layout/media_recommendation_view"/>
+
+ </FrameLayout>
+
+
+ <FrameLayout
+ android:id="@+id/media_cover2_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ >
+
+ <include
+ layout="@layout/media_recommendation_view"/>
+
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/media_cover3_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ >
+
+ <include
+ layout="@layout/media_recommendation_view"/>
+
+ </FrameLayout>
+
+ <include
+ layout="@layout/media_long_press_menu" />
+
+</com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index abc8337..f2e114b 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -106,7 +106,7 @@
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
app:layout_constraintHeight_min="@dimen/min_clickable_item_size">
- <LinearLayout
+ <com.android.systemui.common.ui.view.LaunchableLinearLayout
android:id="@+id/media_seamless_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -135,7 +135,7 @@
android:textDirection="locale"
android:textSize="12sp"
android:lineHeight="16sp" />
- </LinearLayout>
+ </com.android.systemui.common.ui.view.LaunchableLinearLayout>
</LinearLayout>
<!-- Song name -->
diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_call_chip.xml
index c949ba0..18d231c 100644
--- a/packages/SystemUI/res/layout/ongoing_call_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_call_chip.xml
@@ -23,7 +23,7 @@
android:layout_gravity="center_vertical|start"
android:layout_marginStart="5dp"
>
- <LinearLayout
+ <com.android.systemui.common.ui.view.LaunchableLinearLayout
android:id="@+id/ongoing_call_chip_background"
android:layout_width="wrap_content"
android:layout_height="@dimen/ongoing_appops_chip_height"
@@ -55,5 +55,5 @@
android:textColor="?android:attr/colorPrimary"
/>
- </LinearLayout>
+ </com.android.systemui.common.ui.view.LaunchableLinearLayout>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 496eb6e..7e8bc2c 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -31,7 +31,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/actions_container"
app:layout_constraintEnd_toEndOf="@+id/actions_container"
- app:layout_constraintBottom_toTopOf="@id/screenshot_message_container"/>
+ app:layout_constraintBottom_toTopOf="@id/guideline"/>
<HorizontalScrollView
android:id="@+id/actions_container"
android:layout_width="0dp"
@@ -127,57 +127,28 @@
app:layout_constraintTop_toTopOf="@id/screenshot_preview"
android:elevation="7dp"/>
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_end="0dp" />
+
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/screenshot_message_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginVertical="4dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
android:elevation="4dp"
android:background="@drawable/action_chip_container_background"
- android:visibility="gone"
+ android:visibility="invisible"
+ app:layout_constraintTop_toBottomOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintBottom_toBottomOf="parent">
-
- <ImageView
- android:id="@+id/screenshot_message_icon"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:paddingEnd="4dp"
- android:src="@drawable/ic_work_app_badge"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/screenshot_message_content"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"/>
-
- <TextView
- android:id="@+id/screenshot_message_content"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@id/screenshot_message_icon"
- app:layout_constraintEnd_toStartOf="@id/message_dismiss_button"/>
-
- <FrameLayout
- android:id="@+id/message_dismiss_button"
- android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
- android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
- app:layout_constraintStart_toEndOf="@id/screenshot_message_content"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- android:contentDescription="@string/screenshot_dismiss_work_profile">
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="@dimen/overlay_dismiss_button_margin"
- android:src="@drawable/overlay_cancel"/>
- </FrameLayout>
-
+ >
</androidx.constraintlayout.widget.ConstraintLayout>
</com.android.systemui.screenshot.DraggableConstraintLayout>
diff --git a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
new file mode 100644
index 0000000..c794d91
--- /dev/null
+++ b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <ImageView
+ android:id="@+id/screenshot_message_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:paddingEnd="4dp"
+ android:src="@drawable/ic_work_app_badge"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/screenshot_message_content"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"/>
+
+ <TextView
+ android:id="@+id/screenshot_message_content"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@id/screenshot_message_icon"
+ app:layout_constraintEnd_toStartOf="@id/message_dismiss_button"/>
+
+ <FrameLayout
+ android:id="@+id/message_dismiss_button"
+ android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+ android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+ app:layout_constraintStart_toEndOf="@id/screenshot_message_content"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:contentDescription="@string/screenshot_dismiss_work_profile">
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/overlay_dismiss_button_margin"
+ android:src="@drawable/overlay_cancel"/>
+ </FrameLayout>
+</merge>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index dfc0150..2b0021b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -337,7 +337,7 @@
<!-- Used for both start and bottom margin of the preview, relative to the action container -->
<dimen name="overlay_preview_container_margin">8dp</dimen>
<dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
- <dimen name="overlay_action_container_margin_bottom">4dp</dimen>
+ <dimen name="overlay_action_container_margin_bottom">6dp</dimen>
<dimen name="overlay_bg_protection_height">242dp</dimen>
<dimen name="overlay_action_container_corner_radius">18dp</dimen>
<dimen name="overlay_action_container_padding_vertical">4dp</dimen>
@@ -1068,8 +1068,13 @@
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
<dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
<dimen name="qs_media_rec_album_size">88dp</dimen>
+ <dimen name="qs_media_rec_album_width">110dp</dimen>
+ <dimen name="qs_media_rec_album_height_expanded">108dp</dimen>
+ <dimen name="qs_media_rec_album_height_collapsed">77dp</dimen>
<dimen name="qs_media_rec_album_side_margin">16dp</dimen>
<dimen name="qs_media_rec_album_bottom_margin">8dp</dimen>
+ <dimen name="qs_media_rec_album_title_bottom_margin">22dp</dimen>
+ <dimen name="qs_media_rec_album_subtitle_height">12dp</dimen>
<!-- Media tap-to-transfer chip for sender device -->
<dimen name="media_ttt_chip_outer_padding">16dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e60835c..6cdce03 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2360,6 +2360,8 @@
<string name="controls_media_smartspace_rec_item_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
<!-- Description for Smartspace recommendation's media item which doesn't have artist info, including information for the media's title and the source app [CHAR LIMIT=NONE]-->
<string name="controls_media_smartspace_rec_item_no_artist_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%2$s</xliff:g></string>
+ <!-- Header title for Smartspace recommendation card within media controls. [CHAR_LIMIT=30] -->
+ <string name="controls_media_smartspace_rec_header">For You</string>
<!--- ****** Media tap-to-transfer ****** -->
<!-- Text for a button to undo the media transfer. [CHAR LIMIT=20] -->
@@ -2428,6 +2430,8 @@
<string name="media_output_group_title_speakers_and_displays">Speakers & Displays</string>
<!-- Title for Suggested Devices group. [CHAR LIMIT=NONE] -->
<string name="media_output_group_title_suggested_device">Suggested Devices</string>
+ <!-- Sub status indicates device need premium account. [CHAR LIMIT=NONE] -->
+ <string name="media_output_status_require_premium">Requires premium account</string>
<!-- Media Output Broadcast Dialog -->
<!-- Title for Broadcast First Notify Dialog [CHAR LIMIT=60] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9846fc2..58b0234 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -678,6 +678,17 @@
<style name="MediaPlayer.Recommendation"/>
+ <style name="MediaPlayer.Recommendation.Header">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">@dimen/qs_media_padding</item>
+ <item name="android:layout_marginStart">@dimen/qs_media_padding</item>
+ <item name="android:fontFamily">=@*android:string/config_headlineFontFamilyMedium</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
<style name="MediaPlayer.Recommendation.AlbumContainer">
<item name="android:layout_width">@dimen/qs_media_rec_album_size</item>
<item name="android:layout_height">@dimen/qs_media_rec_album_size</item>
@@ -686,6 +697,12 @@
<item name="android:layout_marginBottom">@dimen/qs_media_rec_album_bottom_margin</item>
</style>
+ <style name="MediaPlayer.Recommendation.AlbumContainer.Updated">
+ <item name="android:layout_width">@dimen/qs_media_rec_album_width</item>
+ <item name="android:background">@drawable/qs_media_light_source</item>
+ <item name="android:layout_marginTop">@dimen/qs_media_info_spacing</item>
+ </style>
+
<style name="MediaPlayer.Recommendation.Album">
<item name="android:backgroundTint">@color/media_player_album_bg</item>
</style>
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml b/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml
new file mode 100644
index 0000000..d3be3c7
--- /dev/null
+++ b/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<ConstraintSet
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ >
+
+ <Constraint
+ android:id="@+id/sizing_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_collapsed"
+ />
+
+ <Constraint
+ android:id="@+id/media_rec_title"
+ style="@style/MediaPlayer.Recommendation.Header"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"/>
+
+ <Constraint
+ android:id="@+id/media_cover1_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
+ android:layout_marginEnd="@dimen/qs_media_info_spacing"
+ android:layout_marginStart="@dimen/qs_media_padding"
+ app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/>
+
+
+ <Constraint
+ android:id="@+id/media_cover2_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
+ android:layout_marginEnd="@dimen/qs_media_info_spacing"
+ app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+ app:layout_constraintStart_toEndOf="@id/media_cover1_container"
+ app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/>
+
+ <Constraint
+ android:id="@+id/media_cover3_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
+ android:layout_marginEnd="@dimen/qs_media_padding"
+ app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+ app:layout_constraintStart_toEndOf="@id/media_cover2_container"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+
+</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml b/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml
new file mode 100644
index 0000000..88c7055
--- /dev/null
+++ b/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<ConstraintSet
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ >
+
+ <Constraint
+ android:id="@+id/sizing_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_expanded"
+ />
+
+ <Constraint
+ android:id="@+id/media_rec_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/qs_media_padding"
+ android:layout_marginStart="@dimen/qs_media_padding"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:singleLine="true"
+ android:textSize="14sp"
+ android:textColor="@color/notification_primary_text_color"/>
+
+ <Constraint
+ android:id="@+id/media_cover1_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ android:layout_height="@dimen/qs_media_rec_album_height_expanded"
+ android:layout_marginEnd="@dimen/qs_media_info_spacing"
+ android:layout_marginStart="@dimen/qs_media_padding"
+ app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/>
+
+
+ <Constraint
+ android:id="@+id/media_cover2_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ android:layout_height="@dimen/qs_media_rec_album_height_expanded"
+ android:layout_marginEnd="@dimen/qs_media_info_spacing"
+ app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+ app:layout_constraintStart_toEndOf="@id/media_cover1_container"
+ app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/>
+
+ <Constraint
+ android:id="@+id/media_cover3_container"
+ style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+ android:layout_height="@dimen/qs_media_rec_album_height_expanded"
+ android:layout_marginEnd="@dimen/qs_media_padding"
+ app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+ app:layout_constraintStart_toEndOf="@id/media_cover2_container"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+
+</ConstraintSet>
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 1680b47..3a940e9 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -54,7 +54,6 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
-import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.Executor
@@ -309,15 +308,6 @@
resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
}
- /**
- * Dump information for debugging
- */
- fun dump(pw: PrintWriter) {
- pw.println(this)
- clock?.dump(pw)
- regionSampler?.dump(pw)
- }
-
@VisibleForTesting
internal fun listenForDozeAmount(scope: CoroutineScope): Job {
return scope.launch {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 7f1f941..8684019 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -45,6 +45,7 @@
import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.ClockRegistry;
+import com.android.systemui.shared.regionsampling.RegionSampler;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -445,6 +446,10 @@
if (clock != null) {
clock.dump(pw);
}
+ final RegionSampler regionSampler = mClockEventController.getRegionSampler();
+ if (regionSampler != null) {
+ regionSampler.dump(pw);
+ }
}
/** Gets the animations for the current clock. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 04a2689..53ab6d6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -86,7 +86,7 @@
init {
rippleShader.color = 0xffffffff.toInt() // default color
- rippleShader.progress = 0f
+ rippleShader.rawProgress = 0f
rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
ripplePaint.shader = rippleShader
@@ -269,7 +269,7 @@
duration = AuthRippleController.RIPPLE_ANIMATION_DURATION
addUpdateListener { animator ->
val now = animator.currentPlayTime
- rippleShader.progress = animator.animatedValue as Float
+ rippleShader.rawProgress = animator.animatedValue as Float
rippleShader.time = now.toFloat()
invalidate()
@@ -342,7 +342,7 @@
override fun onDraw(canvas: Canvas?) {
// 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.
+ // animation implementation in the ripple shader. (Twice bigger)
if (drawDwell) {
val maskRadius = (1 - (1 - dwellShader.progress) * (1 - dwellShader.progress) *
(1 - dwellShader.progress)) * dwellRadius * 2f
@@ -351,10 +351,8 @@
}
if (drawRipple) {
- val mask = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * radius * 2f
canvas?.drawCircle(origin.x.toFloat(), origin.y.toFloat(),
- mask, ripplePaint)
+ rippleShader.currentWidth, ripplePaint)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt
new file mode 100644
index 0000000..9763665
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.common.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
+
+/** A [ConstraintLayout] that also implements [LaunchableView]. */
+open class LaunchableConstraintLayout : ConstraintLayout, LaunchableView {
+ private val delegate =
+ LaunchableViewDelegate(
+ this,
+ superSetVisibility = { super.setVisibility(it) },
+ )
+
+ constructor(context: Context) : super(context)
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int
+ ) : super(context, attrs, defStyleAttr)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ defStyleRes: Int
+ ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {
+ delegate.setShouldBlockVisibilityChanges(block)
+ }
+
+ override fun setVisibility(visibility: Int) {
+ delegate.setVisibility(visibility)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
index ddde628..2edac52 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
@@ -23,7 +23,7 @@
import com.android.systemui.animation.LaunchableViewDelegate
/** A [LinearLayout] that also implements [LaunchableView]. */
-class LaunchableLinearLayout : LinearLayout, LaunchableView {
+open class LaunchableLinearLayout : LinearLayout, LaunchableView {
private val delegate =
LaunchableViewDelegate(
this,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 47c41fe..12d36bc 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -365,6 +365,10 @@
// TODO(b/266157412): Tracking Bug
val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
+ // TODO(b/266739309): Tracking Bug
+ @JvmField
+ val MEDIA_RECOMMENDATION_CARD_UPDATE = unreleasedFlag(914, "media_recommendation_card_update")
+
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
index 18fb423..98896b1 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
@@ -37,9 +37,14 @@
private final int mId;
private String mOldClass;
- private ExtensionFragmentListener(View view, String tag, int id, Extension<T> extension) {
+ private ExtensionFragmentListener(
+ FragmentService fragmentService,
+ View view,
+ String tag,
+ int id,
+ Extension<T> extension) {
mTag = tag;
- mFragmentHostManager = FragmentHostManager.get(view);
+ mFragmentHostManager = fragmentService.getFragmentHostManager(view);
mExtension = extension;
mId = id;
mFragmentHostManager.getFragmentManager().beginTransaction()
@@ -61,8 +66,13 @@
mExtension.clearItem(true);
}
- public static <T> void attachExtensonToFragment(View view, String tag, int id,
+ public static <T> void attachExtensonToFragment(
+ FragmentService fragmentService,
+ View view,
+ String tag,
+ int id,
Extension<T> extension) {
- extension.addCallback(new ExtensionFragmentListener(view, tag, id, extension));
+ extension.addCallback(
+ new ExtensionFragmentListener(fragmentService, view, tag, id, extension));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index 9c7411b..6a27ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -36,7 +36,6 @@
import androidx.annotation.NonNull;
import com.android.settingslib.applications.InterestingConfigChanges;
-import com.android.systemui.Dependency;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.util.leak.LeakDetector;
@@ -46,12 +45,17 @@
import java.util.ArrayList;
import java.util.HashMap;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
public class FragmentHostManager {
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Context mContext;
private final HashMap<String, ArrayList<FragmentListener>> mListeners = new HashMap<>();
private final View mRootView;
+ private final LeakDetector mLeakDetector;
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
| ActivityInfo.CONFIG_ASSETS_PATHS);
@@ -61,14 +65,24 @@
private FragmentController mFragments;
private FragmentLifecycleCallbacks mLifecycleCallbacks;
- FragmentHostManager(FragmentService manager, View rootView) {
+ @AssistedInject
+ FragmentHostManager(
+ @Assisted View rootView,
+ FragmentService manager,
+ LeakDetector leakDetector) {
mContext = rootView.getContext();
mManager = manager;
mRootView = rootView;
+ mLeakDetector = leakDetector;
mConfigChanges.applyNewConfig(mContext.getResources());
createFragmentHost(null);
}
+ @AssistedFactory
+ public interface Factory {
+ FragmentHostManager create(View rootView);
+ }
+
private void createFragmentHost(Parcelable savedState) {
mFragments = FragmentController.createController(new HostCallbacks());
mFragments.attachHost(null);
@@ -86,7 +100,7 @@
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment f) {
- Dependency.get(LeakDetector.class).trackGarbage(f);
+ mLeakDetector.trackGarbage(f);
}
};
mFragments.getFragmentManager().registerFragmentLifecycleCallbacks(mLifecycleCallbacks,
@@ -211,19 +225,6 @@
}
}
- public static FragmentHostManager get(View view) {
- try {
- return Dependency.get(FragmentService.class).getFragmentHostManager(view);
- } catch (ClassCastException e) {
- // TODO: Some auto handling here?
- throw e;
- }
- }
-
- public static void removeAndDestroy(View view) {
- Dependency.get(FragmentService.class).removeAndDestroy(view);
- }
-
public void reloadFragments() {
Trace.beginSection("FrargmentHostManager#reloadFragments");
// Save the old state.
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
index fe945fb..d302b13a 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -53,6 +53,7 @@
*/
private final ArrayMap<String, FragmentInstantiationInfo> mInjectionMap = new ArrayMap<>();
private final Handler mHandler = new Handler();
+ private final FragmentHostManager.Factory mFragmentHostManagerFactory;
private ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@@ -67,8 +68,10 @@
@Inject
public FragmentService(
FragmentCreator.Factory fragmentCreatorFactory,
+ FragmentHostManager.Factory fragmentHostManagerFactory,
ConfigurationController configurationController,
DumpManager dumpManager) {
+ mFragmentHostManagerFactory = fragmentHostManagerFactory;
addFragmentInstantiationProvider(fragmentCreatorFactory.build());
configurationController.addCallback(mConfigurationListener);
@@ -152,7 +155,7 @@
public FragmentHostState(View view) {
mView = view;
- mFragmentHostManager = new FragmentHostManager(FragmentService.this, mView);
+ mFragmentHostManager = mFragmentHostManagerFactory.create(mView);
}
public void sendConfigurationChange(Configuration newConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
index 1a10b18..8c1ec16 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
@@ -21,6 +21,7 @@
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
+import com.android.internal.widget.CachingIconView
import com.android.systemui.R
import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.media.controls.ui.IlluminationDrawable
@@ -29,18 +30,15 @@
private const val TAG = "RecommendationViewHolder"
/** ViewHolder for a Smartspace media recommendation. */
-class RecommendationViewHolder private constructor(itemView: View) {
+class RecommendationViewHolder private constructor(itemView: View, updatedView: Boolean) {
val recommendations = itemView as TransitionLayout
// Recommendation screen
- val cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
- val mediaCoverItems =
- listOf<ImageView>(
- itemView.requireViewById(R.id.media_cover1),
- itemView.requireViewById(R.id.media_cover2),
- itemView.requireViewById(R.id.media_cover3)
- )
+ lateinit var cardIcon: ImageView
+ lateinit var mediaAppIcons: List<CachingIconView>
+ lateinit var cardTitle: TextView
+
val mediaCoverContainers =
listOf<ViewGroup>(
itemView.requireViewById(R.id.media_cover1_container),
@@ -48,21 +46,45 @@
itemView.requireViewById(R.id.media_cover3_container)
)
val mediaTitles: List<TextView> =
- listOf(
- itemView.requireViewById(R.id.media_title1),
- itemView.requireViewById(R.id.media_title2),
- itemView.requireViewById(R.id.media_title3)
- )
+ if (updatedView) {
+ mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
+ } else {
+ listOf(
+ itemView.requireViewById(R.id.media_title1),
+ itemView.requireViewById(R.id.media_title2),
+ itemView.requireViewById(R.id.media_title3)
+ )
+ }
val mediaSubtitles: List<TextView> =
- listOf(
- itemView.requireViewById(R.id.media_subtitle1),
- itemView.requireViewById(R.id.media_subtitle2),
- itemView.requireViewById(R.id.media_subtitle3)
- )
+ if (updatedView) {
+ mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
+ } else {
+ listOf(
+ itemView.requireViewById(R.id.media_subtitle1),
+ itemView.requireViewById(R.id.media_subtitle2),
+ itemView.requireViewById(R.id.media_subtitle3)
+ )
+ }
+ val mediaCoverItems: List<ImageView> =
+ if (updatedView) {
+ mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
+ } else {
+ listOf(
+ itemView.requireViewById(R.id.media_cover1),
+ itemView.requireViewById(R.id.media_cover2),
+ itemView.requireViewById(R.id.media_cover3)
+ )
+ }
val gutsViewHolder = GutsViewHolder(itemView)
init {
+ if (updatedView) {
+ mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
+ cardTitle = itemView.requireViewById(R.id.media_rec_title)
+ } else {
+ cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
+ }
(recommendations.background as IlluminationDrawable).let { background ->
mediaCoverContainers.forEach { background.registerLightSource(it) }
background.registerLightSource(gutsViewHolder.cancel)
@@ -83,36 +105,52 @@
* @param parent Parent of inflated view.
*/
@JvmStatic
- fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
+ fun create(
+ inflater: LayoutInflater,
+ parent: ViewGroup,
+ updatedView: Boolean,
+ ): RecommendationViewHolder {
val itemView =
- inflater.inflate(
- R.layout.media_smartspace_recommendations,
- parent,
- false /* attachToRoot */
- )
+ if (updatedView) {
+ inflater.inflate(
+ R.layout.media_recommendations,
+ parent,
+ false /* attachToRoot */
+ )
+ } else {
+ inflater.inflate(
+ R.layout.media_smartspace_recommendations,
+ parent,
+ false /* attachToRoot */
+ )
+ }
// Because this media view (a TransitionLayout) is used to measure and layout the views
// in various states before being attached to its parent, we can't depend on the default
// LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
- return RecommendationViewHolder(itemView)
+ return RecommendationViewHolder(itemView, updatedView)
}
// Res Ids for the control components on the recommendation view.
val controlsIds =
setOf(
R.id.recommendation_card_icon,
+ R.id.media_rec_title,
R.id.media_cover1,
R.id.media_cover2,
R.id.media_cover3,
+ R.id.media_cover,
R.id.media_cover1_container,
R.id.media_cover2_container,
R.id.media_cover3_container,
R.id.media_title1,
R.id.media_title2,
R.id.media_title3,
+ R.id.media_title,
R.id.media_subtitle1,
R.id.media_subtitle2,
- R.id.media_subtitle3
+ R.id.media_subtitle3,
+ R.id.media_subtitle,
)
val mediaTitlesAndSubtitlesIds =
@@ -120,9 +158,11 @@
R.id.media_title1,
R.id.media_title2,
R.id.media_title3,
+ R.id.media_title,
R.id.media_subtitle1,
R.id.media_subtitle2,
- R.id.media_subtitle3
+ R.id.media_subtitle3,
+ R.id.media_subtitle,
)
val mediaContainersIds =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index e7f7647..b2ad155 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -43,6 +43,7 @@
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.controls.ui.MediaControlPanel.SMARTSPACE_CARD_DISMISS_EVENT
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.SmallHash
import com.android.systemui.plugins.ActivityStarter
@@ -88,7 +89,8 @@
falsingManager: FalsingManager,
dumpManager: DumpManager,
private val logger: MediaUiEventLogger,
- private val debugLogger: MediaCarouselControllerLogger
+ private val debugLogger: MediaCarouselControllerLogger,
+ private val mediaFlags: MediaFlags,
) : Dumpable {
/** The current width of the carousel */
private var currentCarouselWidth: Int = 0
@@ -647,7 +649,11 @@
val newRecs = mediaControlPanelFactory.get()
newRecs.attachRecommendation(
- RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
+ RecommendationViewHolder.create(
+ LayoutInflater.from(context),
+ mediaContent,
+ mediaFlags.isRecommendationCardUpdateEnabled()
+ )
)
newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
val lp =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 45d50f0..9250a58 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -61,6 +61,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import androidx.appcompat.content.res.AppCompatResources;
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.annotations.VisibleForTesting;
@@ -752,43 +753,16 @@
int width = mMediaViewHolder.getAlbumView().getMeasuredWidth();
int height = mMediaViewHolder.getAlbumView().getMeasuredHeight();
- // WallpaperColors.fromBitmap takes a good amount of time. We do that work
- // on the background executor to avoid stalling animations on the UI Thread.
mBackgroundExecutor.execute(() -> {
// Album art
ColorScheme mutableColorScheme = null;
Drawable artwork;
boolean isArtworkBound;
Icon artworkIcon = data.getArtwork();
- WallpaperColors wallpaperColors = null;
- if (artworkIcon != null) {
- if (artworkIcon.getType() == Icon.TYPE_BITMAP
- || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
- // Avoids extra processing if this is already a valid bitmap
- wallpaperColors = WallpaperColors
- .fromBitmap(artworkIcon.getBitmap());
- } else {
- Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
- if (artworkDrawable != null) {
- wallpaperColors = WallpaperColors
- .fromDrawable(artworkIcon.loadDrawable(mContext));
- }
- }
- }
+ WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
if (wallpaperColors != null) {
mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
- Drawable albumArt = getScaledBackground(artworkIcon, width, height);
- GradientDrawable gradient = (GradientDrawable) mContext
- .getDrawable(R.drawable.qs_media_scrim);
- gradient.setColors(new int[] {
- ColorUtilKt.getColorWithAlpha(
- MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
- 0.25f),
- ColorUtilKt.getColorWithAlpha(
- MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
- 0.9f),
- });
- artwork = new LayerDrawable(new Drawable[] { albumArt, gradient });
+ artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height);
isArtworkBound = true;
} else {
// If there's no artwork, use colors from the app icon
@@ -867,6 +841,96 @@
});
}
+ private void bindRecommendationArtwork(
+ SmartspaceAction recommendation,
+ String packageName,
+ int itemIndex
+ ) {
+ final int traceCookie = recommendation.hashCode();
+ final String traceName =
+ "MediaControlPanel#bindRecommendationArtwork<" + packageName + ">";
+ Trace.beginAsyncSection(traceName, traceCookie);
+
+ // Capture width & height from views in foreground for artwork scaling in background
+ int width = mRecommendationViewHolder.getMediaCoverContainers().get(0).getMeasuredWidth();
+ int height = mRecommendationViewHolder.getMediaCoverContainers().get(0).getMeasuredHeight();
+
+ mBackgroundExecutor.execute(() -> {
+ // Album art
+ ColorScheme mutableColorScheme = null;
+ Drawable artwork;
+ Icon artworkIcon = recommendation.getIcon();
+ WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
+ if (wallpaperColors != null) {
+ mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
+ artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height);
+ } else {
+ artwork = new ColorDrawable(Color.TRANSPARENT);
+ }
+
+ mMainExecutor.execute(() -> {
+ // Bind the artwork drawable to media cover.
+ ImageView mediaCover =
+ mRecommendationViewHolder.getMediaCoverItems().get(itemIndex);
+ mediaCover.setImageDrawable(artwork);
+
+ // Set up the app icon.
+ ImageView appIconView = mRecommendationViewHolder.getMediaAppIcons().get(itemIndex);
+ appIconView.clearColorFilter();
+ try {
+ Drawable icon = mContext.getPackageManager()
+ .getApplicationIcon(packageName);
+ appIconView.setImageDrawable(icon);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Cannot find icon for package " + packageName, e);
+ appIconView.setImageResource(R.drawable.ic_music_note);
+ }
+ Trace.endAsyncSection(traceName, traceCookie);
+ });
+ });
+ }
+
+ // This method should be called from a background thread. WallpaperColors.fromBitmap takes a
+ // good amount of time. We do that work on the background executor to avoid stalling animations
+ // on the UI Thread.
+ private WallpaperColors getWallpaperColor(Icon artworkIcon) {
+ if (artworkIcon != null) {
+ if (artworkIcon.getType() == Icon.TYPE_BITMAP
+ || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
+ // Avoids extra processing if this is already a valid bitmap
+ return WallpaperColors
+ .fromBitmap(artworkIcon.getBitmap());
+ } else {
+ Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
+ if (artworkDrawable != null) {
+ return WallpaperColors
+ .fromDrawable(artworkIcon.loadDrawable(mContext));
+ }
+ }
+ }
+ return null;
+ }
+
+ private LayerDrawable addGradientToIcon(
+ Icon artworkIcon,
+ ColorScheme mutableColorScheme,
+ int width,
+ int height
+ ) {
+ Drawable albumArt = getScaledBackground(artworkIcon, width, height);
+ GradientDrawable gradient = (GradientDrawable) AppCompatResources
+ .getDrawable(mContext, R.drawable.qs_media_scrim);
+ gradient.setColors(new int[] {
+ ColorUtilKt.getColorWithAlpha(
+ MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
+ 0.25f),
+ ColorUtilKt.getColorWithAlpha(
+ MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
+ 0.9f),
+ });
+ return new LayerDrawable(new Drawable[] { albumArt, gradient });
+ }
+
private void scaleTransitionDrawableLayer(TransitionDrawable transitionDrawable, int layer,
int targetWidth, int targetHeight) {
Drawable drawable = transitionDrawable.getDrawable(layer);
@@ -1224,8 +1288,10 @@
PackageManager packageManager = mContext.getPackageManager();
// Set up media source app's logo.
Drawable icon = packageManager.getApplicationIcon(applicationInfo);
- ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
- headerLogoImageView.setImageDrawable(icon);
+ if (!mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+ ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
+ headerLogoImageView.setImageDrawable(icon);
+ }
fetchAndUpdateRecommendationColors(icon);
// Set up media rec card's tap action if applicable.
@@ -1245,7 +1311,15 @@
// Set up media item cover.
ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex);
- mediaCoverImageView.setImageIcon(recommendation.getIcon());
+ if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+ bindRecommendationArtwork(
+ recommendation,
+ data.getPackageName(),
+ itemIndex
+ );
+ } else {
+ mediaCoverImageView.setImageIcon(recommendation.getIcon());
+ }
// Set up the media item's click listener if applicable.
ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex);
@@ -1275,7 +1349,6 @@
recommendation.getTitle(), artistName, appName));
}
-
// Set up title
CharSequence title = recommendation.getTitle();
hasTitle |= !TextUtils.isEmpty(title);
@@ -1353,6 +1426,10 @@
int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme);
int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme);
+ if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+ mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
+ }
+
mRecommendationViewHolder.getRecommendations()
.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
mRecommendationViewHolder.getMediaTitles().forEach(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 2ec7be6..1e6002c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -25,6 +25,7 @@
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.calculateAlpha
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.animation.MeasurementOutput
import com.android.systemui.util.animation.TransitionLayout
@@ -45,7 +46,8 @@
private val context: Context,
private val configurationController: ConfigurationController,
private val mediaHostStatesManager: MediaHostStatesManager,
- private val logger: MediaViewLogger
+ private val logger: MediaViewLogger,
+ private val mediaFlags: MediaFlags,
) {
/**
@@ -646,8 +648,13 @@
expandedLayout.load(context, R.xml.media_session_expanded)
}
TYPE.RECOMMENDATION -> {
- collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
- expandedLayout.load(context, R.xml.media_recommendation_expanded)
+ if (mediaFlags.isRecommendationCardUpdateEnabled()) {
+ collapsedLayout.load(context, R.xml.media_recommendations_view_collapsed)
+ expandedLayout.load(context, R.xml.media_recommendations_view_expanded)
+ } else {
+ collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
+ expandedLayout.load(context, R.xml.media_recommendation_expanded)
+ }
}
}
refreshState()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index ab03930..81efa36 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -51,4 +51,8 @@
* whether the underlying notification was dismissed
*/
fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
+
+ /** Check whether we show the updated recommendation card. */
+ fun isRecommendationCardUpdateEnabled() =
+ featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 51b5a3d..769e0c8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,17 +16,21 @@
package com.android.systemui.media.dialog;
+import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
+import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
import androidx.core.widget.CompoundButtonCompat;
import androidx.recyclerview.widget.RecyclerView;
@@ -186,6 +190,17 @@
mCurrentActivePosition = position;
updateFullItemClickListener(v -> onItemClick(v, device));
setSingleLineLayout(getItemTitle(device));
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+ && mController.isSubStatusSupported() && device.hasDisabledReason()) {
+ //update to subtext with device status
+ setUpDeviceIcon(device);
+ mSubTitleText.setText(
+ Api34Impl.composeDisabledReason(device.getDisableReason(), mContext));
+ updateConnectionFailedStatusIcon();
+ updateFullItemClickListener(null);
+ setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */,
+ false /* showProgressBar */, true /* showSubtitle */,
+ true /* showStatus */);
} else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
setUpDeviceIcon(device);
updateConnectionFailedStatusIcon();
@@ -389,4 +404,12 @@
mTitleText.setText(groupDividerTitle);
}
}
+
+ @RequiresApi(34)
+ private static class Api34Impl {
+ @DoNotInline
+ static String composeDisabledReason(int reason, Context context) {
+ return "";
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 4e08050..dc75538 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -45,6 +45,7 @@
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settingslib.Utils;
@@ -142,11 +143,12 @@
final TextView mVolumeValueText;
final ImageView mTitleIcon;
final ProgressBar mProgressBar;
- final MediaOutputSeekbar mSeekBar;
final LinearLayout mTwoLineLayout;
final ImageView mStatusIcon;
final CheckBox mCheckBox;
final ViewGroup mEndTouchArea;
+ @VisibleForTesting
+ MediaOutputSeekbar mSeekBar;
private String mDeviceId;
private ValueAnimator mCornerAnimator;
private ValueAnimator mVolumeAnimator;
@@ -390,6 +392,7 @@
mTitleIcon.setVisibility(View.VISIBLE);
mVolumeValueText.setVisibility(View.GONE);
}
+ mController.logInteractionAdjustVolume(device);
mIsDragging = false;
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index f95da27..5f5c686 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -141,7 +141,7 @@
@VisibleForTesting
LocalMediaManager mLocalMediaManager;
@VisibleForTesting
- private MediaOutputMetricLogger mMetricLogger;
+ MediaOutputMetricLogger mMetricLogger;
private int mCurrentState;
private int mColorItemContent;
@@ -757,6 +757,10 @@
return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_ROUTES_PROCESSING);
}
+ public boolean isSubStatusSupported() {
+ return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_DEVICE_STATUS);
+ }
+
List<MediaDevice> getGroupMediaDevices() {
final List<MediaDevice> selectedDevices = getSelectedMediaDevice();
final List<MediaDevice> selectableDevices = getSelectableMediaDevice();
@@ -866,12 +870,15 @@
}
void adjustVolume(MediaDevice device, int volume) {
- mMetricLogger.logInteractionAdjustVolume(device);
ThreadUtils.postOnBackgroundThread(() -> {
device.requestSetVolume(volume);
});
}
+ void logInteractionAdjustVolume(MediaDevice device) {
+ mMetricLogger.logInteractionAdjustVolume(device);
+ }
+
String getPackageName() {
return mPackageName;
}
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 f8785fc..f1acae8 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
@@ -79,9 +79,9 @@
animator.addUpdateListener { updateListener ->
val now = updateListener.currentPlayTime
val progress = updateListener.animatedValue as Float
- rippleShader.progress = startingPercentage + (progress * (1 - startingPercentage))
- rippleShader.distortionStrength = 1 - rippleShader.progress
- rippleShader.pixelDensity = 1 - rippleShader.progress
+ rippleShader.rawProgress = startingPercentage + (progress * (1 - startingPercentage))
+ rippleShader.distortionStrength = 1 - rippleShader.rawProgress
+ rippleShader.pixelDensity = 1 - rippleShader.rawProgress
rippleShader.time = now.toFloat()
invalidate()
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 1edb837..e665d83 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -34,8 +34,8 @@
import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.mediaprojection.devicepolicy.MediaProjectionDevicePolicyModule
+import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.Binds
@@ -54,13 +54,12 @@
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle
-@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile
-
-@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile
-
@Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
-@Module(subcomponents = [MediaProjectionAppSelectorComponent::class])
+@Module(
+ subcomponents = [MediaProjectionAppSelectorComponent::class],
+ includes = [MediaProjectionDevicePolicyModule::class]
+)
interface MediaProjectionModule {
@Binds
@IntoMap
@@ -110,20 +109,6 @@
): ConfigurationController = ConfigurationControllerImpl(activity)
@Provides
- @PersonalProfile
- @MediaProjectionAppSelectorScope
- fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle {
- // Current foreground user is the 'personal' profile
- return UserHandle.of(activityManagerWrapper.currentUserId)
- }
-
- @Provides
- @WorkProfile
- @MediaProjectionAppSelectorScope
- fun workProfileUserHandle(userTracker: UserTracker): UserHandle? =
- userTracker.userProfiles.find { it.isManagedProfile }?.userHandle
-
- @Provides
@HostUserHandle
@MediaProjectionAppSelectorScope
fun hostUserHandle(activity: MediaProjectionAppSelectorActivity): UserHandle {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt
new file mode 100644
index 0000000..829b0dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.appselector
+
+import android.content.Context
+import android.os.UserHandle
+import com.android.internal.R as AndroidR
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
+import com.android.internal.app.ResolverListAdapter
+import com.android.systemui.R
+import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
+import javax.inject.Inject
+
+@MediaProjectionAppSelectorScope
+class MediaProjectionBlockerEmptyStateProvider
+@Inject
+constructor(
+ @HostUserHandle private val hostAppHandle: UserHandle,
+ @PersonalProfile private val personalProfileHandle: UserHandle,
+ private val policyResolver: ScreenCaptureDevicePolicyResolver,
+ private val context: Context
+) : EmptyStateProvider {
+
+ override fun getEmptyState(resolverListAdapter: ResolverListAdapter): EmptyState? {
+ val screenCaptureAllowed =
+ policyResolver.isScreenCaptureAllowed(
+ targetAppUserHandle = resolverListAdapter.userHandle,
+ hostAppUserHandle = hostAppHandle
+ )
+
+ val isHostAppInPersonalProfile = hostAppHandle == personalProfileHandle
+
+ val subtitle =
+ if (isHostAppInPersonalProfile) {
+ AndroidR.string.resolver_cant_share_with_personal_apps_explanation
+ } else {
+ AndroidR.string.resolver_cant_share_with_work_apps_explanation
+ }
+
+ if (!screenCaptureAllowed) {
+ return object : EmptyState {
+ override fun getSubtitle(): String = context.resources.getString(subtitle)
+ override fun getTitle(): String =
+ context.resources.getString(
+ R.string.screen_capturing_disabled_by_policy_dialog_title
+ )
+ override fun onEmptyStateShown() {
+ // TODO(b/237397740) report analytics
+ }
+ override fun shouldSkipDataRebuild(): Boolean = true
+ }
+ }
+ return null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt
new file mode 100644
index 0000000..13b71a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.devicepolicy
+
+import android.os.UserHandle
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import dagger.Module
+import dagger.Provides
+import javax.inject.Qualifier
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile
+
+/** Module for media projection device policy related dependencies */
+@Module
+class MediaProjectionDevicePolicyModule {
+ @Provides
+ @PersonalProfile
+ fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle {
+ // Current foreground user is the 'personal' profile
+ return UserHandle.of(activityManagerWrapper.currentUserId)
+ }
+
+ @Provides
+ @WorkProfile
+ fun workProfileUserHandle(userTracker: UserTracker): UserHandle? =
+ userTracker.userProfiles.find { it.isManagedProfile }?.userHandle
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt
new file mode 100644
index 0000000..6bd33e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.devicepolicy
+
+import android.app.admin.DevicePolicyManager
+import android.os.UserHandle
+import android.os.UserManager
+import javax.inject.Inject
+
+/**
+ * Utility class to resolve if screen capture allowed for a particular target app/host app pair. It
+ * caches the state of the policies, so you need to create a new instance of this class if you want
+ * to react to updated policies state.
+ */
+class ScreenCaptureDevicePolicyResolver
+@Inject
+constructor(
+ private val devicePolicyManager: DevicePolicyManager,
+ private val userManager: UserManager,
+ @PersonalProfile private val personalProfileUserHandle: UserHandle,
+ @WorkProfile private val workProfileUserHandle: UserHandle?
+) {
+
+ /**
+ * Returns true if [hostAppUserHandle] is allowed to perform screen capture of
+ * [targetAppUserHandle]
+ */
+ fun isScreenCaptureAllowed(
+ targetAppUserHandle: UserHandle,
+ hostAppUserHandle: UserHandle,
+ ): Boolean {
+ if (hostAppUserHandle.isWorkProfile() && workProfileScreenCaptureDisabled) {
+ // Disable screen capturing as host apps should not capture the screen
+ return false
+ }
+
+ if (!hostAppUserHandle.isWorkProfile() && personalProfileScreenCaptureDisabled) {
+ // Disable screen capturing as personal apps should not capture the screen
+ return false
+ }
+
+ if (targetAppUserHandle.isWorkProfile()) {
+ // Work profile target
+ if (workProfileScreenCaptureDisabled) {
+ // Do not allow sharing work profile apps as work profile capturing is disabled
+ return false
+ }
+ } else {
+ // Personal profile target
+ if (hostAppUserHandle.isWorkProfile() && disallowSharingIntoManagedProfile) {
+ // Do not allow sharing of personal apps into work profile apps
+ return false
+ }
+
+ if (personalProfileScreenCaptureDisabled) {
+ // Disable screen capturing as personal apps should not be captured
+ return false
+ }
+ }
+
+ return true
+ }
+
+ /**
+ * Returns true if [hostAppUserHandle] is NOT allowed to capture an app from any profile,
+ * could be useful to finish the screen capture flow as soon as possible when the screen
+ * could not be captured at all.
+ */
+ fun isScreenCaptureCompletelyDisabled(hostAppUserHandle: UserHandle): Boolean {
+ val isWorkAppsCaptureDisabled =
+ if (workProfileUserHandle != null) {
+ !isScreenCaptureAllowed(
+ targetAppUserHandle = workProfileUserHandle,
+ hostAppUserHandle = hostAppUserHandle
+ )
+ } else true
+
+ val isPersonalAppsCaptureDisabled =
+ !isScreenCaptureAllowed(
+ targetAppUserHandle = personalProfileUserHandle,
+ hostAppUserHandle = hostAppUserHandle
+ )
+
+ return isWorkAppsCaptureDisabled && isPersonalAppsCaptureDisabled
+ }
+
+ private val personalProfileScreenCaptureDisabled: Boolean by lazy {
+ devicePolicyManager.getScreenCaptureDisabled(
+ /* admin */ null,
+ personalProfileUserHandle.identifier
+ )
+ }
+
+ private val workProfileScreenCaptureDisabled: Boolean by lazy {
+ workProfileUserHandle?.let {
+ devicePolicyManager.getScreenCaptureDisabled(/* admin */ null, it.identifier)
+ }
+ ?: false
+ }
+
+ private val disallowSharingIntoManagedProfile: Boolean by lazy {
+ workProfileUserHandle?.let {
+ userManager.hasUserRestrictionForUser(
+ UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
+ it
+ )
+ }
+ ?: false
+ }
+
+ private fun UserHandle?.isWorkProfile(): Boolean = this == workProfileUserHandle
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
new file mode 100644
index 0000000..a6b3da0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.devicepolicy
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/** Dialog that shows that screen capture is disabled on this device. */
+class ScreenCaptureDisabledDialog(context: Context) : SystemUIDialog(context) {
+
+ init {
+ setTitle(context.getString(R.string.screen_capturing_disabled_by_policy_dialog_title))
+ setMessage(
+ context.getString(R.string.screen_capturing_disabled_by_policy_dialog_description)
+ )
+ setIcon(R.drawable.ic_cast)
+ setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> cancel() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 2a6ca1a..8ad2f86 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -17,11 +17,11 @@
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
-import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.settingslib.Utils
import com.android.systemui.R
+import com.android.systemui.animation.LaunchableFrameLayout
import com.android.systemui.statusbar.events.BackgroundAnimatableView
class OngoingPrivacyChip @JvmOverloads constructor(
@@ -29,7 +29,7 @@
attrs: AttributeSet? = null,
defStyleAttrs: Int = 0,
defStyleRes: Int = 0
-) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
+) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
private var iconMargin = 0
private var iconSize = 0
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 100853c..98af9df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -314,7 +314,6 @@
if (!TILES_SETTING.equals(key)) {
return;
}
- Log.d(TAG, "Recreating tiles");
if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
}
@@ -327,6 +326,7 @@
}
}
if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
+ Log.d(TAG, "Recreating tiles: " + tileSpecs);
mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
tile -> {
Log.d(TAG, "Destroying tile: " + tile.getKey());
@@ -372,6 +372,8 @@
Log.d(TAG, "Destroying not available tile: " + tileSpec);
mQSLogger.logTileDestroyed(tileSpec, "Tile not available");
}
+ } else {
+ Log.d(TAG, "No factory for a spec: " + tileSpec);
}
} catch (Throwable t) {
Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
new file mode 100644
index 0000000..1e531ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -0,0 +1,113 @@
+package com.android.systemui.screenshot
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.drawable.Drawable
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
+import android.view.ViewTreeObserver
+import android.view.animation.AccelerateDecelerateInterpolator
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.constraintlayout.widget.Guideline
+import com.android.systemui.R
+
+/**
+ * MessageContainerController controls the display of content in the screenshot message container.
+ */
+class MessageContainerController
+constructor(
+ parent: ViewGroup,
+) {
+ private val guideline: Guideline = parent.requireViewById(R.id.guideline)
+ private val messageContainer: ViewGroup =
+ parent.requireViewById(R.id.screenshot_message_container)
+
+ /**
+ * Show a notification under the screenshot view indicating that a work profile screenshot has
+ * been taken and which app can be used to view it.
+ *
+ * @param appName The name of the app to use to view screenshots
+ * @param appIcon Optional icon for the relevant files app
+ * @param onDismiss Runnable to be run when the user dismisses this message
+ */
+ fun showWorkProfileMessage(appName: CharSequence, appIcon: Drawable?, onDismiss: Runnable) {
+ // Eventually this container will support multiple notification types, but for now just make
+ // sure we don't double inflate.
+ if (messageContainer.childCount == 0) {
+ View.inflate(
+ messageContainer.context,
+ R.layout.screenshot_work_profile_first_run,
+ messageContainer
+ )
+ }
+ if (appIcon != null) {
+ // Replace the default icon if one is provided.
+ val imageView: ImageView =
+ messageContainer.requireViewById<ImageView>(R.id.screenshot_message_icon)
+ imageView.setImageDrawable(appIcon)
+ }
+ val messageContent =
+ messageContainer.requireViewById<TextView>(R.id.screenshot_message_content)
+ messageContent.text =
+ messageContainer.context.getString(
+ R.string.screenshot_work_profile_notification,
+ appName
+ )
+ messageContainer.requireViewById<View>(R.id.message_dismiss_button).setOnClickListener {
+ animateOutMessageContainer()
+ onDismiss.run()
+ }
+
+ // Need the container to be fully measured before animating in (to know animation offset
+ // destination)
+ messageContainer.viewTreeObserver.addOnPreDrawListener(
+ object : ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ messageContainer.viewTreeObserver.removeOnPreDrawListener(this)
+ animateInMessageContainer()
+ return false
+ }
+ }
+ )
+ }
+
+ private fun animateInMessageContainer() {
+ if (messageContainer.visibility == View.VISIBLE) return
+
+ messageContainer.visibility = View.VISIBLE
+ getAnimator(true).start()
+ }
+
+ private fun animateOutMessageContainer() {
+ getAnimator(false).apply {
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ super.onAnimationEnd(animation)
+ messageContainer.visibility = View.INVISIBLE
+ }
+ }
+ )
+ start()
+ }
+ }
+
+ private fun getAnimator(animateIn: Boolean): Animator {
+ val params = messageContainer.layoutParams as MarginLayoutParams
+ val offset = messageContainer.height + params.topMargin + params.bottomMargin
+ val anim = if (animateIn) ValueAnimator.ofFloat(0f, 1f) else ValueAnimator.ofFloat(1f, 0f)
+ with(anim) {
+ duration = ScreenshotView.SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS
+ interpolator = AccelerateDecelerateInterpolator()
+ addUpdateListener { valueAnimator: ValueAnimator ->
+ val interpolation = valueAnimator.animatedValue as Float
+ guideline.setGuidelineEnd((interpolation * offset).toInt())
+ messageContainer.alpha = interpolation
+ }
+ }
+ return anim
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 9f7d4f0..ab13962 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -293,6 +293,7 @@
};
private ScreenshotView mScreenshotView;
+ private MessageContainerController mMessageContainerController;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
private boolean mScreenshotTakenInPortrait;
@@ -631,6 +632,7 @@
// Inflate the screenshot layout
mScreenshotView = (ScreenshotView)
LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
+ mMessageContainerController = new MessageContainerController(mScreenshotView);
mScreenshotView.addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
@Override
@@ -1193,7 +1195,8 @@
private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
mScreenshotView.setChipIntents(imageData);
if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
- mWorkProfileMessageController.onScreenshotTaken(imageData.owner, mScreenshotView);
+ mWorkProfileMessageController.onScreenshotTaken(imageData.owner,
+ mMessageContainerController);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 8b73a57f..afba7ad 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -33,7 +33,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
@@ -82,7 +81,6 @@
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
@@ -102,8 +100,7 @@
* Handles the visual elements and animations for the screenshot flow.
*/
public class ScreenshotView extends FrameLayout implements
- ViewTreeObserver.OnComputeInternalInsetsListener,
- WorkProfileMessageController.WorkProfileMessageDisplay {
+ ViewTreeObserver.OnComputeInternalInsetsListener {
interface ScreenshotViewCallback {
void onUserInteraction();
@@ -123,7 +120,7 @@
private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234;
private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500;
private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
- private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
+ public static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
@@ -142,8 +139,6 @@
private ImageView mScrollingScrim;
private DraggableConstraintLayout mScreenshotStatic;
- private ViewGroup mMessageContainer;
- private TextView mMessageContent;
private ImageView mScreenshotPreview;
private ImageView mScreenshotBadge;
private View mScreenshotPreviewBorder;
@@ -295,8 +290,11 @@
mDismissButton.getBoundsOnScreen(tmpRect);
swipeRegion.op(tmpRect, Region.Op.UNION);
- mMessageContainer.findViewById(R.id.message_dismiss_button).getBoundsOnScreen(tmpRect);
- swipeRegion.op(tmpRect, Region.Op.UNION);
+ View messageDismiss = findViewById(R.id.message_dismiss_button);
+ if (messageDismiss != null) {
+ messageDismiss.getBoundsOnScreen(tmpRect);
+ swipeRegion.op(tmpRect, Region.Op.UNION);
+ }
return swipeRegion;
}
@@ -352,39 +350,11 @@
}
}
- /**
- * Show a notification under the screenshot view indicating that a work profile screenshot has
- * been taken and which app can be used to view it.
- *
- * @param appName The name of the app to use to view screenshots
- * @param appIcon Optional icon for the relevant files app
- * @param onDismiss Runnable to be run when the user dismisses this message
- */
- @Override
- public void showWorkProfileMessage(CharSequence appName, @Nullable Drawable appIcon,
- Runnable onDismiss) {
- if (appIcon != null) {
- // Replace the default icon if one is provided.
- ImageView imageView = mMessageContainer.findViewById(R.id.screenshot_message_icon);
- imageView.setImageDrawable(appIcon);
- }
- mMessageContent.setText(
- mContext.getString(R.string.screenshot_work_profile_notification, appName));
- mMessageContainer.setVisibility(VISIBLE);
- mMessageContainer.findViewById(R.id.message_dismiss_button).setOnClickListener((v) -> {
- mMessageContainer.setVisibility(View.GONE);
- onDismiss.run();
- });
- }
-
@Override // View
protected void onFinishInflate() {
+ super.onFinishInflate();
mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
mScreenshotStatic = requireNonNull(findViewById(R.id.screenshot_static));
- mMessageContainer =
- requireNonNull(mScreenshotStatic.findViewById(R.id.screenshot_message_container));
- mMessageContent =
- requireNonNull(mMessageContainer.findViewById(R.id.screenshot_message_content));
mScreenshotPreview = requireNonNull(findViewById(R.id.screenshot_preview));
mScreenshotPreviewBorder = requireNonNull(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
index 5d7e56f..b4a07d4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -40,10 +40,10 @@
) {
/**
- * Determine if a message should be shown to the user, send message details to messageDisplay if
- * appropriate.
+ * Determine if a message should be shown to the user, send message details to
+ * MessageContainerController if appropriate.
*/
- fun onScreenshotTaken(userHandle: UserHandle, messageDisplay: WorkProfileMessageDisplay) {
+ fun onScreenshotTaken(userHandle: UserHandle, messageContainer: MessageContainerController) {
if (userManager.isManagedProfile(userHandle.identifier) && !messageAlreadyDismissed()) {
var badgedIcon: Drawable? = null
var label: CharSequence? = null
@@ -65,7 +65,9 @@
val badgedLabel =
packageManager.getUserBadgedLabel(label ?: defaultFileAppName(), userHandle)
- messageDisplay.showWorkProfileMessage(badgedLabel, badgedIcon) { onMessageDismissed() }
+ messageContainer.showWorkProfileMessage(badgedLabel, badgedIcon) {
+ onMessageDismissed()
+ }
}
}
@@ -89,15 +91,6 @@
private fun defaultFileAppName() = context.getString(R.string.screenshot_default_files_app_name)
- /** UI that can show work profile messages (ScreenshotView in practice) */
- interface WorkProfileMessageDisplay {
- /**
- * Show the given message and icon, calling onDismiss if the user explicitly dismisses the
- * message.
- */
- fun showWorkProfileMessage(text: CharSequence, icon: Drawable?, onDismiss: Runnable)
- }
-
companion object {
const val TAG = "WorkProfileMessageCtrl"
const val SHARED_PREFERENCES_NAME = "com.android.systemui.screenshot"
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 85b259e..de02115 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -31,6 +31,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.qs.QSContainerController
@@ -54,6 +55,7 @@
private val largeScreenShadeHeaderController: LargeScreenShadeHeaderController,
private val shadeExpansionStateManager: ShadeExpansionStateManager,
private val featureFlags: FeatureFlags,
+ private val fragmentService: FragmentService,
@Main private val delayableExecutor: DelayableExecutor
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
@@ -128,6 +130,7 @@
mView.setInsetsChangedListener(delayedInsetSetter)
mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) }
mView.setConfigurationChangedListener { updateResources() }
+ fragmentService.getFragmentHostManager(mView).addTagListener(QS.TAG, mView)
}
override fun onViewDetached() {
@@ -136,6 +139,7 @@
mView.removeOnInsetsChangedListener()
mView.removeQSFragmentAttachedListener()
mView.setConfigurationChangedListener(null)
+ fragmentService.getFragmentHostManager(mView).removeTagListener(QS.TAG, mView)
}
fun updateResources() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index 02316b7..f73dde6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -29,7 +29,6 @@
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.systemui.R;
-import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
@@ -133,18 +132,6 @@
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- FragmentHostManager.get(this).addTagListener(QS.TAG, this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- FragmentHostManager.get(this).removeTagListener(QS.TAG, this);
- }
-
- @Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mInsetsChangedListener.accept(insets);
return insets;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
index 87c12c2..6577cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
@@ -20,10 +20,21 @@
import android.util.AttributeSet;
import android.widget.Button;
+import com.android.systemui.animation.LaunchableView;
+import com.android.systemui.animation.LaunchableViewDelegate;
+
+import kotlin.Unit;
+
/**
* A Button which doesn't have overlapping drawing commands
*/
-public class AlphaOptimizedButton extends Button {
+public class AlphaOptimizedButton extends Button implements LaunchableView {
+ private LaunchableViewDelegate mDelegate = new LaunchableViewDelegate(this,
+ (visibility) -> {
+ super.setVisibility(visibility);
+ return Unit.INSTANCE;
+ });
+
public AlphaOptimizedButton(Context context) {
super(context);
}
@@ -45,4 +56,14 @@
public boolean hasOverlappingRendering() {
return false;
}
+
+ @Override
+ public void setShouldBlockVisibilityChanges(boolean block) {
+ mDelegate.setShouldBlockVisibilityChanges(block);
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ mDelegate.setVisibility(visibility);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 5ff755e..85399ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1306,8 +1306,13 @@
// Set up the quick settings tile panel
final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
if (container != null) {
- FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
- ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
+ FragmentHostManager fragmentHostManager =
+ mFragmentService.getFragmentHostManager(container);
+ ExtensionFragmentListener.attachExtensonToFragment(
+ mFragmentService,
+ container,
+ QS.TAG,
+ R.id.qs_frame,
mExtensionController
.newExtension(QS.class)
.withPlugin(QS.class)
@@ -1478,7 +1483,9 @@
}
protected QS createDefaultQSFragment() {
- return FragmentHostManager.get(mNotificationShadeWindowView).create(QSFragment.class);
+ return mFragmentService
+ .getFragmentHostManager(mNotificationShadeWindowView)
+ .create(QSFragment.class);
}
private void setUpPresenter() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
index 2c8677d..2d80edb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
@@ -19,14 +19,14 @@
import android.content.Context
import android.util.AttributeSet
import android.widget.ImageView
-import android.widget.LinearLayout
import android.widget.TextView
import com.android.systemui.R
+import com.android.systemui.common.ui.view.LaunchableLinearLayout
class StatusBarUserSwitcherContainer(
context: Context?,
attrs: AttributeSet?
-) : LinearLayout(context, attrs) {
+) : LaunchableLinearLayout(context, attrs) {
lateinit var text: TextView
private set
lateinit var avatar: ImageView
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index e0d780a..7a4e35f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -48,6 +48,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentService;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener;
@@ -73,6 +74,7 @@
private boolean mIsAttached;
private final ViewGroup mStatusBarWindowView;
+ private final FragmentService mFragmentService;
// The container in which we should run launch animations started from the status bar and
// expanding into the opening window.
private final ViewGroup mLaunchAnimationContainer;
@@ -86,6 +88,7 @@
WindowManager windowManager,
IWindowManager iWindowManager,
StatusBarContentInsetsProvider contentInsetsProvider,
+ FragmentService fragmentService,
@Main Resources resources,
Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) {
mContext = context;
@@ -93,6 +96,7 @@
mIWindowManager = iWindowManager;
mContentInsetsProvider = contentInsetsProvider;
mStatusBarWindowView = statusBarWindowView;
+ mFragmentService = fragmentService;
mLaunchAnimationContainer = mStatusBarWindowView.findViewById(
R.id.status_bar_launch_animation_container);
mLpChanged = new WindowManager.LayoutParams();
@@ -157,7 +161,7 @@
/** Returns a fragment host manager for the status bar window view. */
public FragmentHostManager getFragmentHostManager() {
- return FragmentHostManager.get(mStatusBarWindowView);
+ return mFragmentService.getFragmentHostManager(mStatusBarWindowView);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java
index 79811c5..2475890 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java
@@ -28,8 +28,9 @@
import androidx.preference.PreferenceScreen;
import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentService;
import java.util.Objects;
@@ -74,7 +75,7 @@
RadioFragment f = new RadioFragment();
f.setPreference(this);
- FragmentHostManager.get(v).getFragmentManager()
+ Dependency.get(FragmentService.class).getFragmentHostManager(v).getFragmentManager()
.beginTransaction()
.add(android.R.id.content, f)
.commit();
@@ -86,8 +87,10 @@
Bundle savedInstanceState) {
super.onDialogStateRestored(fragment, dialog, savedInstanceState);
View view = dialog.findViewById(R.id.content);
- RadioFragment radioFragment = (RadioFragment) FragmentHostManager.get(view)
- .getFragmentManager().findFragmentById(R.id.content);
+ RadioFragment radioFragment = (RadioFragment) Dependency.get(FragmentService.class)
+ .getFragmentHostManager(view)
+ .getFragmentManager()
+ .findFragmentById(R.id.content);
if (radioFragment != null) {
radioFragment.setPreference(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
index 08ee0af..56c5d3b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
@@ -27,6 +27,8 @@
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
import com.android.systemui.statusbar.CrossFadeHelper
/**
@@ -38,7 +40,7 @@
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
-) : ConstraintLayout(context, attrs, defStyleAttr) {
+) : ConstraintLayout(context, attrs, defStyleAttr), LaunchableView {
private val boundsRect = Rect()
private val originalGoneChildrenSet: MutableSet<Int> = mutableSetOf()
@@ -50,7 +52,11 @@
private var desiredMeasureWidth = 0
private var desiredMeasureHeight = 0
- private var transitionVisibility = View.VISIBLE
+ private val delegate =
+ LaunchableViewDelegate(
+ this,
+ superSetVisibility = { super.setVisibility(it) },
+ )
/**
* The measured state of this view which is the one we will lay ourselves out with. This
@@ -83,11 +89,12 @@
}
}
- override fun setTransitionVisibility(visibility: Int) {
- // We store the last transition visibility assigned to this view to restore it later if
- // necessary.
- super.setTransitionVisibility(visibility)
- transitionVisibility = visibility
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {
+ delegate.setShouldBlockVisibilityChanges(block)
+ }
+
+ override fun setVisibility(visibility: Int) {
+ delegate.setVisibility(visibility)
}
override fun onFinishInflate() {
@@ -173,14 +180,6 @@
translationY = currentState.translation.y
CrossFadeHelper.fadeIn(this, currentState.alpha)
-
- // CrossFadeHelper#fadeIn will change this view visibility, which overrides the transition
- // visibility. We set the transition visibility again to make sure that this view plays well
- // with GhostView, which sets the transition visibility and is used for activity launch
- // animations.
- if (transitionVisibility != View.VISIBLE) {
- setTransitionVisibility(transitionVisibility)
- }
}
private fun applyCurrentStateOnPredraw() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 00b2fbe..b9c23d4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -15,7 +15,6 @@
*/
package com.android.keyguard
-import com.android.systemui.statusbar.CommandQueue
import android.content.BroadcastReceiver
import android.testing.AndroidTestingRunner
import android.view.View
@@ -34,6 +33,7 @@
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.mockito.any
@@ -41,8 +41,6 @@
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 java.util.concurrent.Executor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
@@ -57,8 +55,10 @@
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
+import java.util.*
+import java.util.concurrent.Executor
+import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidTestingRunner::class)
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index a61cd23..578e1d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -15,6 +15,7 @@
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
import android.view.ViewGroup
+import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -26,6 +27,7 @@
import junit.framework.AssertionFailedError
import kotlin.concurrent.thread
import org.junit.After
+import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -195,6 +197,13 @@
verify(controller).onLaunchAnimationStart(anyBoolean())
}
+ @Test
+ fun creatingControllerFromNormalViewThrows() {
+ assertThrows(IllegalArgumentException::class.java) {
+ ActivityLaunchAnimator.Controller.fromView(FrameLayout(mContext))
+ }
+ }
+
private fun fakeWindow(): RemoteAnimationTarget {
val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */)
val taskInfo = ActivityManager.RunningTaskInfo()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index cac4a0e..1e62fd23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -26,6 +26,7 @@
import junit.framework.Assert.assertTrue
import org.junit.After
import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -260,6 +261,13 @@
assertThat(touchSurface.visibility).isEqualTo(View.GONE)
}
+ @Test
+ fun creatingControllerFromNormalViewThrows() {
+ assertThrows(IllegalArgumentException::class.java) {
+ DialogLaunchAnimator.Controller.fromView(FrameLayout(mContext))
+ }
+ }
+
private fun createAndShowDialog(
animator: DialogLaunchAnimator = dialogLaunchAnimator,
): TestDialog {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
index 3696ec5..0798d73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
@@ -16,58 +16,34 @@
package com.android.systemui.animation
-import android.graphics.drawable.Drawable
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewParent
+import android.widget.FrameLayout
import androidx.test.filters.SmallTest
-import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.SysuiTestCase
-import org.junit.Before
+import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
- @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
- @Mock lateinit var view: View
- @Mock lateinit var rootView: ViewGroup
- @Mock lateinit var viewParent: ViewParent
- @Mock lateinit var drawable: Drawable
- lateinit var controller: GhostedViewLaunchAnimatorController
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- whenever(view.rootView).thenReturn(rootView)
- whenever(view.background).thenReturn(drawable)
- whenever(view.height).thenReturn(0)
- whenever(view.width).thenReturn(0)
- whenever(view.parent).thenReturn(viewParent)
- whenever(view.visibility).thenReturn(View.VISIBLE)
- whenever(view.invalidate()).then { /* NO-OP */ }
- whenever(view.getLocationOnScreen(any())).then { /* NO-OP */ }
- whenever(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true)
- whenever(interactionJankMonitor.end(anyInt())).thenReturn(true)
- controller = GhostedViewLaunchAnimatorController(view, 0, interactionJankMonitor)
- }
-
@Test
fun animatingOrphanViewDoesNotCrash() {
val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
+ val controller = GhostedViewLaunchAnimatorController(LaunchableFrameLayout(mContext))
controller.onIntentStarted(willAnimate = true)
controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
controller.onLaunchAnimationProgress(state, progress = 0f, linearProgress = 0f)
controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
}
+
+ @Test
+ fun creatingControllerFromNormalViewThrows() {
+ assertThrows(IllegalArgumentException::class.java) {
+ GhostedViewLaunchAnimatorController(FrameLayout(mContext))
+ }
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 3b8bb80..dfb4d5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -31,6 +31,8 @@
import android.os.RemoteException;
import android.service.dreams.IDreamOverlay;
import android.service.dreams.IDreamOverlayCallback;
+import android.service.dreams.IDreamOverlayClient;
+import android.service.dreams.IDreamOverlayClientCallback;
import android.testing.AndroidTestingRunner;
import android.view.View;
import android.view.ViewGroup;
@@ -58,6 +60,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@SmallTest
@@ -148,13 +151,25 @@
mDreamOverlayCallbackController);
}
- @Test
- public void testOnStartMetricsLogged() throws Exception {
+ public IDreamOverlayClient getClient() throws RemoteException {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+ final IDreamOverlayClientCallback callback =
+ Mockito.mock(IDreamOverlayClientCallback.class);
+ overlay.getClient(callback);
+ final ArgumentCaptor<IDreamOverlayClient> clientCaptor =
+ ArgumentCaptor.forClass(IDreamOverlayClient.class);
+ verify(callback).onDreamOverlayClient(clientCaptor.capture());
+
+ return clientCaptor.getValue();
+ }
+
+ @Test
+ public void testOnStartMetricsLogged() throws Exception {
+ final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -165,11 +180,10 @@
@Test
public void testOverlayContainerViewAddedToWindow() throws Exception {
- final IBinder proxy = mService.onBind(new Intent());
- final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+ final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -178,11 +192,10 @@
@Test
public void testDreamOverlayContainerViewControllerInitialized() throws Exception {
- final IBinder proxy = mService.onBind(new Intent());
- final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+ final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -196,11 +209,10 @@
.thenReturn(mDreamOverlayContainerViewParent)
.thenReturn(null);
- final IBinder proxy = mService.onBind(new Intent());
- final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+ final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -209,11 +221,10 @@
@Test
public void testShouldShowComplicationsSetByStartDream() throws RemoteException {
- final IBinder proxy = mService.onBind(new Intent());
- final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+ final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
true /*shouldShowComplication*/);
assertThat(mService.shouldShowComplications()).isTrue();
@@ -221,11 +232,10 @@
@Test
public void testLowLightSetByStartDream() throws RemoteException {
- final IBinder proxy = mService.onBind(new Intent());
- final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+ final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback,
+ client.startDream(mWindowParams, mDreamOverlayCallback,
LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -235,11 +245,10 @@
@Test
public void testOnEndDream() throws RemoteException {
- final IBinder proxy = mService.onBind(new Intent());
- final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+ final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback,
+ client.startDream(mWindowParams, mDreamOverlayCallback,
LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -261,11 +270,10 @@
@Test
public void testDestroy() throws RemoteException {
- final IBinder proxy = mService.onBind(new Intent());
- final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+ final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback,
+ client.startDream(mWindowParams, mDreamOverlayCallback,
LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -305,15 +313,14 @@
@Test
public void testDecorViewNotAddedToWindowAfterDestroy() throws Exception {
- final IBinder proxy = mService.onBind(new Intent());
- final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+ final IDreamOverlayClient client = getClient();
// Destroy the service.
mService.onDestroy();
mMainExecutor.runAllReady();
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -331,11 +338,10 @@
@Test
public void testResetCurrentOverlayWhenConnectedToNewDream() throws RemoteException {
- final IBinder proxy = mService.onBind(new Intent());
- final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+ final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting. Do not show dream complications.
- overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -352,7 +358,7 @@
// New dream starting with dream complications showing. Note that when a new dream is
// binding to the dream overlay service, it receives the same instance of IBinder as the
// first one.
- overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
true /*shouldShowComplication*/);
mMainExecutor.runAllReady();
@@ -371,11 +377,10 @@
@Test
public void testWakeUp() throws RemoteException {
- final IBinder proxy = mService.onBind(new Intent());
- final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+ final IDreamOverlayClient client = getClient();
// Inform the overlay service of dream starting.
- overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
true /*shouldShowComplication*/);
mMainExecutor.runAllReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 89c7280..a4cf15c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -31,13 +31,13 @@
import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.view.View;
-import android.widget.ImageView;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.common.ui.view.LaunchableImageView;
import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.controller.ControlsController;
import com.android.systemui.controls.controller.StructureInfo;
@@ -90,7 +90,7 @@
private View mView;
@Mock
- private ImageView mHomeControlsView;
+ private LaunchableImageView mHomeControlsView;
@Mock
private ActivityStarter mActivityStarter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
index 77c837b..a2dc1eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
@@ -14,6 +14,7 @@
@SmallTest
class FragmentServiceTest : SysuiTestCase() {
private val fragmentCreator = TestFragmentCreator()
+ private val fragmenetHostManagerFactory: FragmentHostManager.Factory = mock()
private val fragmentCreatorFactory = FragmentService.FragmentCreator.Factory { fragmentCreator }
private lateinit var fragmentService: FragmentService
@@ -24,7 +25,13 @@
Looper.prepare()
}
- fragmentService = FragmentService(fragmentCreatorFactory, mock(), DumpManager())
+ fragmentService =
+ FragmentService(
+ fragmentCreatorFactory,
+ fragmenetHostManagerFactory,
+ mock(),
+ DumpManager()
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index e4e95e5..5e5dc8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.controls.ui.MediaHierarchyManager.Companion.LOCATION_QS
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -87,6 +88,7 @@
@Mock lateinit var smartspaceMediaData: SmartspaceMediaData
@Mock lateinit var mediaCarousel: MediaScrollView
@Mock lateinit var pageIndicator: PageIndicator
+ @Mock lateinit var mediaFlags: MediaFlags
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
@Captor
lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener>
@@ -114,7 +116,8 @@
falsingManager,
dumpManager,
logger,
- debugLogger
+ debugLogger,
+ mediaFlags,
)
verify(configurationController).addCallback(capture(configListener))
verify(mediaDataManager).addListener(capture(listener))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index b35dd26..ce22b19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -204,6 +204,9 @@
@Mock private lateinit var coverContainer1: ViewGroup
@Mock private lateinit var coverContainer2: ViewGroup
@Mock private lateinit var coverContainer3: ViewGroup
+ @Mock private lateinit var recAppIconItem: CachingIconView
+ @Mock private lateinit var recCardTitle: TextView
+ @Mock private lateinit var coverItem: ImageView
private lateinit var coverItem1: ImageView
private lateinit var coverItem2: ImageView
private lateinit var coverItem3: ImageView
@@ -220,6 +223,7 @@
this.set(Flags.UMO_TURBULENCE_NOISE, false)
this.set(Flags.MEDIA_FALSING_PENALTY, true)
this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true)
+ this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
}
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -2059,6 +2063,51 @@
}
@Test
+ fun bindRecommendation_setAfterExecutors() {
+ fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
+ whenever(recommendationViewHolder.mediaAppIcons)
+ .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
+ whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
+ whenever(recommendationViewHolder.mediaCoverItems)
+ .thenReturn(listOf(coverItem, coverItem, coverItem))
+
+ val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bmp)
+ canvas.drawColor(Color.RED)
+ val albumArt = Icon.createWithBitmap(bmp)
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", "title1")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "title3")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
+ )
+
+ player.attachRecommendation(recommendationViewHolder)
+ player.bindRecommendation(data)
+ bgExecutor.runAllReady()
+ mainExecutor.runAllReady()
+
+ verify(recCardTitle).setTextColor(any<Int>())
+ verify(recAppIconItem, times(3)).setImageDrawable(any(Drawable::class.java))
+ verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java))
+ }
+
+ @Test
fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() {
fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
val semanticActions =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 4ed6d7c..2f7eac2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -22,6 +22,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionViewState
@@ -55,6 +56,7 @@
@Mock private lateinit var mediaTitleWidgetState: WidgetState
@Mock private lateinit var mediaSubTitleWidgetState: WidgetState
@Mock private lateinit var mediaContainerWidgetState: WidgetState
+ @Mock private lateinit var mediaFlags: MediaFlags
val delta = 0.1F
@@ -64,7 +66,13 @@
fun setup() {
MockitoAnnotations.initMocks(this)
mediaViewController =
- MediaViewController(context, configurationController, mediaHostStatesManager, logger)
+ MediaViewController(
+ context,
+ configurationController,
+ mediaHostStatesManager,
+ logger,
+ mediaFlags,
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 7c3c9d2..8fd15c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -29,6 +29,7 @@
import android.testing.AndroidTestingRunner;
import android.view.View;
import android.widget.LinearLayout;
+import android.widget.SeekBar;
import androidx.core.graphics.drawable.IconCompat;
import androidx.test.filters.SmallTest;
@@ -43,6 +44,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import java.util.ArrayList;
import java.util.List;
@@ -57,6 +60,7 @@
private static final String TEST_DEVICE_ID_1 = "test_device_id_1";
private static final String TEST_DEVICE_ID_2 = "test_device_id_2";
private static final String TEST_SESSION_NAME = "test_session_name";
+
private static final int TEST_MAX_VOLUME = 20;
private static final int TEST_CURRENT_VOLUME = 10;
@@ -69,6 +73,8 @@
private IconCompat mIconCompat = mock(IconCompat.class);
private View mDialogLaunchView = mock(View.class);
+ @Captor
+ private ArgumentCaptor<SeekBar.OnSeekBarChangeListener> mOnSeekBarChangeListenerCaptor;
private MediaOutputAdapter mMediaOutputAdapter;
private MediaOutputAdapter.MediaDeviceViewHolder mViewHolder;
private List<MediaDevice> mMediaDevices = new ArrayList<>();
@@ -78,6 +84,7 @@
@Before
public void setUp() {
when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(false);
+ when(mMediaOutputController.isSubStatusSupported()).thenReturn(false);
when(mMediaOutputController.getMediaItemList()).thenReturn(mMediaItems);
when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices);
when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
@@ -348,6 +355,24 @@
}
@Test
+ public void onBindViewHolder_dragSeekbar_setsVolume() {
+ mOnSeekBarChangeListenerCaptor = ArgumentCaptor.forClass(
+ SeekBar.OnSeekBarChangeListener.class);
+ MediaOutputSeekbar mSpySeekbar = spy(mViewHolder.mSeekBar);
+ mViewHolder.mSeekBar = mSpySeekbar;
+ when(mMediaDevice1.getMaxVolume()).thenReturn(TEST_MAX_VOLUME);
+ when(mMediaDevice1.getCurrentVolume()).thenReturn(TEST_MAX_VOLUME);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ verify(mViewHolder.mSeekBar).setOnSeekBarChangeListener(
+ mOnSeekBarChangeListenerCaptor.capture());
+
+ mOnSeekBarChangeListenerCaptor.getValue().onStopTrackingTouch(mViewHolder.mSeekBar);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ verify(mMediaOutputController).logInteractionAdjustVolume(mMediaDevice1);
+ }
+
+ @Test
public void onBindViewHolder_bindSelectableDevice_verifyView() {
List<MediaDevice> selectableDevices = new ArrayList<>();
selectableDevices.add(mMediaDevice2);
@@ -404,6 +429,24 @@
}
@Test
+ public void subStatusSupported_onBindViewHolder_bindFailedStateDevice_verifyView() {
+ String deviceStatus = "";
+ when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
+ when(mMediaDevice2.hasDisabledReason()).thenReturn(true);
+ when(mMediaDevice2.getDisableReason()).thenReturn(-1);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mSubTitleText.getText()).isEqualTo(deviceStatus);
+ assertThat(mViewHolder.mTwoLineTitleText.getText()).isEqualTo(TEST_DEVICE_NAME_2);
+ }
+
+ @Test
public void onBindViewHolder_inTransferring_bindTransferringDevice_verifyView() {
when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true);
when(mMediaDevice1.getState()).thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 117751c..7c36e46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -93,6 +93,11 @@
private static final String TEST_SONG = "test_song";
private static final String TEST_SESSION_ID = "test_session_id";
private static final String TEST_SESSION_NAME = "test_session_name";
+ private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+ private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock(
+ ActivityLaunchAnimator.Controller.class);
+ private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
+ NearbyMediaDevicesManager.class);
// Mock
private MediaController mMediaController = mock(MediaController.class);
private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
@@ -111,12 +116,7 @@
private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
- private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private FeatureFlags mFlags = mock(FeatureFlags.class);
- private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock(
- ActivityLaunchAnimator.Controller.class);
- private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
- NearbyMediaDevicesManager.class);
private View mDialogLaunchView = mock(View.class);
private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class);
@@ -522,6 +522,17 @@
}
@Test
+ public void logInteractionAdjustVolume_triggersFromMetricLogger() {
+ MediaOutputMetricLogger spyMediaOutputMetricLogger = spy(
+ mMediaOutputController.mMetricLogger);
+ mMediaOutputController.mMetricLogger = spyMediaOutputMetricLogger;
+
+ mMediaOutputController.logInteractionAdjustVolume(mMediaDevice1);
+
+ verify(spyMediaOutputMetricLogger).logInteractionAdjustVolume(mMediaDevice1);
+ }
+
+ @Test
public void getSessionVolumeMax_triggersFromLocalMediaManager() {
mMediaOutputController.getSessionVolumeMax();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt
new file mode 100644
index 0000000..e8b6f9b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.devicepolicy
+
+import android.app.admin.DevicePolicyManager
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.any
+
+abstract class BaseScreenCaptureDevicePolicyResolverTest(private val precondition: Preconditions) :
+ SysuiTestCase() {
+
+ abstract class Preconditions(
+ val personalScreenCaptureDisabled: Boolean,
+ val workScreenCaptureDisabled: Boolean,
+ val disallowShareIntoManagedProfile: Boolean
+ )
+
+ protected val devicePolicyManager: DevicePolicyManager = mock()
+ protected val userManager: UserManager = mock()
+
+ protected val personalUserHandle: UserHandle = UserHandle.of(123)
+ protected val workUserHandle: UserHandle = UserHandle.of(456)
+
+ protected val policyResolver =
+ ScreenCaptureDevicePolicyResolver(
+ devicePolicyManager,
+ userManager,
+ personalUserHandle,
+ workUserHandle
+ )
+
+ @Before
+ fun setUp() {
+ setUpPolicies()
+ }
+
+ private fun setUpPolicies() {
+ whenever(
+ devicePolicyManager.getScreenCaptureDisabled(
+ any(),
+ eq(personalUserHandle.identifier)
+ )
+ )
+ .thenReturn(precondition.personalScreenCaptureDisabled)
+
+ whenever(devicePolicyManager.getScreenCaptureDisabled(any(), eq(workUserHandle.identifier)))
+ .thenReturn(precondition.workScreenCaptureDisabled)
+
+ whenever(
+ userManager.hasUserRestrictionForUser(
+ eq(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE),
+ eq(workUserHandle)
+ )
+ )
+ .thenReturn(precondition.disallowShareIntoManagedProfile)
+ }
+}
+
+@RunWith(Parameterized::class)
+@SmallTest
+class IsAllowedScreenCaptureDevicePolicyResolverTest(
+ private val test: IsScreenCaptureAllowedTestCase
+) : BaseScreenCaptureDevicePolicyResolverTest(test.given) {
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() =
+ listOf(
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false,
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true,
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false,
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true,
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = true,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ IsScreenCaptureAllowedTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ isTargetInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureAllowed = false,
+ ),
+ )
+ }
+
+ class Preconditions(
+ personalScreenCaptureDisabled: Boolean,
+ workScreenCaptureDisabled: Boolean,
+ disallowShareIntoManagedProfile: Boolean,
+ val isHostInWorkProfile: Boolean,
+ val isTargetInWorkProfile: Boolean,
+ ) :
+ BaseScreenCaptureDevicePolicyResolverTest.Preconditions(
+ personalScreenCaptureDisabled,
+ workScreenCaptureDisabled,
+ disallowShareIntoManagedProfile
+ )
+
+ data class IsScreenCaptureAllowedTestCase(
+ val given: Preconditions,
+ val expectedScreenCaptureAllowed: Boolean
+ ) {
+ override fun toString(): String =
+ "isScreenCaptureAllowed: " +
+ "host[${if (given.isHostInWorkProfile) "work" else "personal"} profile], " +
+ "target[${if (given.isTargetInWorkProfile) "work" else "personal"} profile], " +
+ "personal screen capture disabled = ${given.personalScreenCaptureDisabled}, " +
+ "work screen capture disabled = ${given.workScreenCaptureDisabled}, " +
+ "disallow share into managed profile = ${given.disallowShareIntoManagedProfile}, " +
+ "expected screen capture allowed = $expectedScreenCaptureAllowed"
+ }
+
+ @Test
+ fun test() {
+ val targetAppUserHandle =
+ if (test.given.isTargetInWorkProfile) workUserHandle else personalUserHandle
+ val hostAppUserHandle =
+ if (test.given.isHostInWorkProfile) workUserHandle else personalUserHandle
+
+ val screenCaptureAllowed =
+ policyResolver.isScreenCaptureAllowed(targetAppUserHandle, hostAppUserHandle)
+
+ assertWithMessage("Screen capture policy resolved incorrectly")
+ .that(screenCaptureAllowed)
+ .isEqualTo(test.expectedScreenCaptureAllowed)
+ }
+}
+
+@RunWith(Parameterized::class)
+@SmallTest
+class IsCompletelyNotAllowedScreenCaptureDevicePolicyResolverTest(
+ private val test: IsScreenCaptureCompletelyDisabledTestCase
+) : BaseScreenCaptureDevicePolicyResolverTest(test.given) {
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() =
+ listOf(
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = false,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = false,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = false,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = false,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = false
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ ),
+ IsScreenCaptureCompletelyDisabledTestCase(
+ given =
+ Preconditions(
+ isHostInWorkProfile = true,
+ personalScreenCaptureDisabled = true,
+ workScreenCaptureDisabled = true,
+ disallowShareIntoManagedProfile = true
+ ),
+ expectedScreenCaptureCompletelyDisabled = true,
+ )
+ )
+ }
+
+ class Preconditions(
+ personalScreenCaptureDisabled: Boolean,
+ workScreenCaptureDisabled: Boolean,
+ disallowShareIntoManagedProfile: Boolean,
+ val isHostInWorkProfile: Boolean,
+ ) :
+ BaseScreenCaptureDevicePolicyResolverTest.Preconditions(
+ personalScreenCaptureDisabled,
+ workScreenCaptureDisabled,
+ disallowShareIntoManagedProfile
+ )
+
+ data class IsScreenCaptureCompletelyDisabledTestCase(
+ val given: Preconditions,
+ val expectedScreenCaptureCompletelyDisabled: Boolean
+ ) {
+ override fun toString(): String =
+ "isScreenCaptureCompletelyDisabled: " +
+ "host[${if (given.isHostInWorkProfile) "work" else "personal"} profile], " +
+ "personal screen capture disabled = ${given.personalScreenCaptureDisabled}, " +
+ "work screen capture disabled = ${given.workScreenCaptureDisabled}, " +
+ "disallow share into managed profile = ${given.disallowShareIntoManagedProfile}, " +
+ "expected screen capture completely disabled = $expectedScreenCaptureCompletelyDisabled"
+ }
+
+ @Test
+ fun test() {
+ val hostAppUserHandle =
+ if (test.given.isHostInWorkProfile) workUserHandle else personalUserHandle
+
+ val completelyDisabled = policyResolver.isScreenCaptureCompletelyDisabled(hostAppUserHandle)
+
+ assertWithMessage("Screen capture policy resolved incorrectly")
+ .that(completelyDisabled)
+ .isEqualTo(test.expectedScreenCaptureCompletelyDisabled)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index ad4f84d..8644b5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -35,6 +35,7 @@
import com.android.internal.logging.MetricsLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.LaunchableFrameLayout
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
@@ -342,7 +343,7 @@
val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
tile.qsTile.activityLaunchForClick = pi
- tile.handleClick(mock(View::class.java))
+ tile.handleClick(mock(LaunchableFrameLayout::class.java))
testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
index bd04b3c..e8905ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -65,7 +65,7 @@
@Mock
private Context mContext;
@Mock
- private WorkProfileMessageController.WorkProfileMessageDisplay mMessageDisplay;
+ private MessageContainerController mMessageDisplay;
@Mock
private Drawable mActivityIcon;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
index bdafc7d..c915502a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
@@ -13,8 +13,11 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.plugins.qs.QS
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
import com.android.systemui.util.concurrency.FakeExecutor
@@ -29,6 +32,7 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.eq
@@ -69,6 +73,10 @@
private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
@Mock
private lateinit var featureFlags: FeatureFlags
+ @Mock
+ private lateinit var fragmentService: FragmentService
+ @Mock
+ private lateinit var fragmentHostManager: FragmentHostManager
@Captor
lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
@Captor
@@ -77,6 +85,8 @@
lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
@Captor
lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
+ @Captor
+ lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
private lateinit var controller: NotificationsQSContainerController
private lateinit var navigationModeCallback: ModeChangedListener
@@ -91,8 +101,10 @@
mContext.ensureTestableResources()
whenever(notificationsQSContainer.context).thenReturn(mContext)
whenever(notificationsQSContainer.resources).thenReturn(mContext.resources)
+ whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
+
controller = NotificationsQSContainerController(
notificationsQSContainer,
navigationModeController,
@@ -100,6 +112,7 @@
largeScreenShadeHeaderController,
shadeExpansionStateManager,
featureFlags,
+ fragmentService,
delayableExecutor
)
@@ -114,9 +127,10 @@
doNothing().`when`(notificationsQSContainer)
.setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
doNothing().`when`(notificationsQSContainer).applyConstraints(constraintSetCaptor.capture())
-
+ doNothing().`when`(notificationsQSContainer)
+ .addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
controller.init()
- controller.onViewAttached()
+ attachStateListenerCaptor.value.onViewAttachedToWindow(notificationsQSContainer)
navigationModeCallback = navigationModeCaptor.value
taskbarVisibilityCallback = taskbarVisibilityCaptor.value
@@ -385,6 +399,7 @@
largeScreenShadeHeaderController,
shadeExpansionStateManager,
featureFlags,
+ fragmentService,
delayableExecutor
)
controller.updateConstraints()
@@ -426,6 +441,17 @@
verify(largeScreenShadeHeaderController).startCustomizingAnimation(false, 100L)
}
+ @Test
+ fun testTagListenerAdded() {
+ verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(notificationsQSContainer))
+ }
+
+ @Test
+ fun testTagListenerRemoved() {
+ attachStateListenerCaptor.value.onViewDetachedFromWindow(notificationsQSContainer)
+ verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(notificationsQSContainer))
+ }
+
private fun disableSplitShade() {
setSplitShadeEnabled(false)
}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 3584f16..ac78228 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -51,6 +51,7 @@
import android.app.GameModeInfo;
import android.app.GameState;
import android.app.IGameManagerService;
+import android.app.IUidObserver;
import android.app.compat.PackageOverride;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -118,6 +119,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Service to manage game related features.
@@ -171,40 +173,19 @@
private final ArrayMap<String, GamePackageConfiguration> mOverrideConfigs = new ArrayMap<>();
@Nullable
private final GameServiceController mGameServiceController;
+ private final Object mUidObserverLock = new Object();
+ @VisibleForTesting
+ @Nullable
+ final UidObserver mUidObserver;
+ @GuardedBy("mUidObserverLock")
+ private final Set<Integer> mForegroundGameUids = new HashSet<>();
public GameManagerService(Context context) {
this(context, createServiceThread().getLooper());
}
GameManagerService(Context context, Looper looper) {
- mContext = context;
- mHandler = new SettingsHandler(looper);
- mPackageManager = mContext.getPackageManager();
- mUserManager = mContext.getSystemService(UserManager.class);
- mPlatformCompat = IPlatformCompat.Stub.asInterface(
- ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
- mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
- mSystemDir = new File(Environment.getDataDirectory(), "system");
- mSystemDir.mkdirs();
- FileUtils.setPermissions(mSystemDir.toString(),
- FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
- -1, -1);
- mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir,
- GAME_MODE_INTERVENTION_LIST_FILE_NAME));
- FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(),
- FileUtils.S_IRUSR | FileUtils.S_IWUSR
- | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
- -1, -1);
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
- mGameServiceController = new GameServiceController(
- context, BackgroundThread.getExecutor(),
- new GameServiceProviderSelectorImpl(
- context.getResources(),
- context.getPackageManager()),
- new GameServiceProviderInstanceFactoryImpl(context));
- } else {
- mGameServiceController = null;
- }
+ this(context, looper, Environment.getDataDirectory());
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -237,6 +218,14 @@
} else {
mGameServiceController = null;
}
+ mUidObserver = new UidObserver();
+ try {
+ ActivityManager.getService().registerUidObserver(mUidObserver,
+ ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
+ ActivityManager.PROCESS_STATE_UNKNOWN, null);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Could not register UidObserver");
+ }
}
@Override
@@ -1874,4 +1863,66 @@
* load dynamic library for frame rate overriding JNI calls
*/
private static native void nativeSetOverrideFrameRate(int uid, float frameRate);
+
+ final class UidObserver extends IUidObserver.Stub {
+ @Override
+ public void onUidIdle(int uid, boolean disabled) {}
+
+ @Override
+ public void onUidGone(int uid, boolean disabled) {
+ synchronized (mUidObserverLock) {
+ disableGameMode(uid);
+ }
+ }
+
+ @Override
+ public void onUidActive(int uid) {}
+
+ @Override
+ public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
+ synchronized (mUidObserverLock) {
+ if (ActivityManager.isProcStateBackground(procState)) {
+ disableGameMode(uid);
+ return;
+ }
+
+ final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+ if (packages == null || packages.length == 0) {
+ return;
+ }
+
+ final int userId = mContext.getUserId();
+ if (!Arrays.stream(packages).anyMatch(p -> isPackageGame(p, userId))) {
+ return;
+ }
+
+ if (mForegroundGameUids.isEmpty()) {
+ Slog.v(TAG, "Game power mode ON (process state was changed to foreground)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, true);
+ }
+ mForegroundGameUids.add(uid);
+ }
+ }
+
+ private void disableGameMode(int uid) {
+ synchronized (mUidObserverLock) {
+ if (!mForegroundGameUids.contains(uid)) {
+ return;
+ }
+ mForegroundGameUids.remove(uid);
+ if (!mForegroundGameUids.isEmpty()) {
+ return;
+ }
+ Slog.v(TAG,
+ "Game power mode OFF (process remomved or state changed to background)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, false);
+ }
+ }
+
+ @Override
+ public void onUidCachedChanged(int uid, boolean cached) {}
+
+ @Override
+ public void onUidProcAdjChanged(int uid) {}
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6084cca..dfb2467 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3637,8 +3637,19 @@
return false;
}
+ /**
+ * Update stream volume, ringer mode and mute status after a volume index change
+ * @param streamType
+ * @param index
+ * @param flags
+ * @param device the device for which the volume is changed
+ * @param caller
+ * @param hasModifyAudioSettings
+ * @param canChangeMute true if the origin of this event is one where the mute state should be
+ * updated following the change in volume index
+ */
private void onSetStreamVolume(int streamType, int index, int flags, int device,
- String caller, boolean hasModifyAudioSettings) {
+ String caller, boolean hasModifyAudioSettings, boolean canChangeMute) {
final int stream = mStreamVolumeAlias[streamType];
setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings);
// setting volume on ui sounds stream type also controls silent mode
@@ -3648,10 +3659,8 @@
TAG + ".onSetStreamVolume", false /*external*/);
}
// setting non-zero volume for a muted stream unmutes the stream and vice versa
- // (only when changing volume for the current device),
// except for BT SCO stream where only explicit mute is allowed to comply to BT requirements
- if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO)
- && (getDeviceForStream(stream) == device)) {
+ if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO) && canChangeMute) {
// As adjustStreamVolume with muteAdjust flags mute/unmutes stream and aliased streams.
muteAliasStreams(stream, index == 0);
}
@@ -4366,7 +4375,10 @@
mPendingVolumeCommand = new StreamVolumeCommand(
streamType, index, flags, device);
} else {
- onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings);
+ onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings,
+ // ada is non-null when called from setDeviceVolume,
+ // which shouldn't update the mute state
+ ada == null /*canChangeMute*/);
index = mStreamStates[streamType].getIndex(device);
}
}
@@ -10275,7 +10287,8 @@
mPendingVolumeCommand.mIndex,
mPendingVolumeCommand.mFlags,
mPendingVolumeCommand.mDevice,
- callingPackage, true /*hasModifyAudioSettings*/);
+ callingPackage, true /*hasModifyAudioSettings*/,
+ true /*canChangeMute*/);
mPendingVolumeCommand = null;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 41721ce..2ebf8d9 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -439,6 +439,9 @@
// finished destroying itself.
private static final int DESTROY_TIMEOUT = 10 * 1000;
+ // Rounding tolerance to be used in aspect ratio computations
+ private static final float ASPECT_RATIO_ROUNDING_TOLERANCE = 0.005f;
+
final ActivityTaskManagerService mAtmService;
@NonNull
final ActivityInfo info; // activity info provided by developer in AndroidManifest
@@ -8239,8 +8242,12 @@
if (screenResolvedBounds.width() <= parentAppBounds.width()) {
float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier(
newParentConfiguration);
- offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width())
- * positionMultiplier);
+ offsetX = Math.max(0, (int) Math.ceil((parentAppBounds.width()
+ - screenResolvedBounds.width()) * positionMultiplier)
+ // This is added to make sure that insets added inside
+ // CompatDisplayInsets#getContainerBounds() do not break the alignment
+ // provided by the positionMultiplier
+ - screenResolvedBounds.left + parentAppBounds.left);
}
}
@@ -8250,8 +8257,12 @@
if (screenResolvedBounds.height() <= parentAppBounds.height()) {
float positionMultiplier = mLetterboxUiController.getVerticalPositionMultiplier(
newParentConfiguration);
- offsetY = (int) Math.ceil((parentAppBounds.height() - screenResolvedBounds.height())
- * positionMultiplier);
+ offsetY = Math.max(0, (int) Math.ceil((parentAppBounds.height()
+ - screenResolvedBounds.height()) * positionMultiplier)
+ // This is added to make sure that insets added inside
+ // CompatDisplayInsets#getContainerBounds() do not break the alignment
+ // provided by the positionMultiplier
+ - screenResolvedBounds.top + parentAppBounds.top);
}
}
@@ -8950,7 +8961,7 @@
int activityWidth = containingAppWidth;
int activityHeight = containingAppHeight;
- if (containingRatio > desiredAspectRatio) {
+ if (containingRatio - desiredAspectRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
if (containingAppWidth < containingAppHeight) {
// Width is the shorter side, so we use that to figure-out what the max. height
// should be given the aspect ratio.
@@ -8960,7 +8971,7 @@
// should be given the aspect ratio.
activityWidth = (int) ((activityHeight * desiredAspectRatio) + 0.5f);
}
- } else if (containingRatio < desiredAspectRatio) {
+ } else if (desiredAspectRatio - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
boolean adjustWidth;
switch (getRequestedConfigurationOrientation()) {
case ORIENTATION_LANDSCAPE:
@@ -10034,6 +10045,7 @@
isLandscape ? shortSide : longSide);
}
+ // TODO(b/267151420): Explore removing getContainerBounds() from CompatDisplayInsets.
/** Gets the horizontal centered container bounds for size compatibility mode. */
void getContainerBounds(Rect outAppBounds, Rect outBounds, int rotation, int orientation,
boolean orientationRequested, boolean isFixedToUserRotation) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f8fd2b9..cf7d5d9 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2007,7 +2007,8 @@
dc.getDisplayPolicy().simulateLayoutDisplay(df);
final InsetsState insetsState = df.mInsetsState;
final Rect displayFrame = insetsState.getDisplayFrame();
- final Insets decor = calculateDecorInsetsWithInternalTypes(insetsState);
+ final Insets decor = insetsState.calculateInsets(displayFrame, DECOR_TYPES,
+ true /* ignoreVisibility */);
final Insets statusBar = insetsState.calculateInsets(displayFrame,
Type.statusBars(), true /* ignoreVisibility */);
mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
@@ -2039,17 +2040,8 @@
}
}
- // TODO (b/235842600): Use public type once we can treat task bar as navigation bar.
- static final int[] INTERNAL_DECOR_TYPES;
- static {
- final ArraySet<Integer> decorTypes = InsetsState.toInternalType(
- Type.displayCutout() | Type.navigationBars());
- decorTypes.remove(ITYPE_EXTRA_NAVIGATION_BAR);
- INTERNAL_DECOR_TYPES = new int[decorTypes.size()];
- for (int i = 0; i < INTERNAL_DECOR_TYPES.length; i++) {
- INTERNAL_DECOR_TYPES[i] = decorTypes.valueAt(i);
- }
- }
+
+ static final int DECOR_TYPES = Type.displayCutout() | Type.navigationBars();
private final DisplayContent mDisplayContent;
private final Info[] mInfoForRotation = new Info[4];
@@ -2076,20 +2068,6 @@
info.mNeedUpdate = true;
}
}
-
- // TODO (b/235842600): Remove this method once we can treat task bar as navigation bar.
- private static Insets calculateDecorInsetsWithInternalTypes(InsetsState state) {
- final Rect frame = state.getDisplayFrame();
- Insets insets = Insets.NONE;
- for (int i = INTERNAL_DECOR_TYPES.length - 1; i >= 0; i--) {
- final InsetsSource source = state.peekSource(INTERNAL_DECOR_TYPES[i]);
- if (source != null) {
- insets = Insets.max(source.calculateInsets(frame, true /* ignoreVisibility */),
- insets);
- }
- }
- return insets;
- }
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 6f7ff5c..3404279 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -249,11 +249,13 @@
mDeskDockRotation = readRotation(R.integer.config_deskDockRotation);
mUndockedHdmiRotation = readRotation(R.integer.config_undockedHdmiRotation);
- mRotation = readDefaultDisplayRotation(displayAddress);
+ int defaultRotation = readDefaultDisplayRotation(displayAddress);
+ mRotation = defaultRotation;
if (isDefaultDisplay) {
final Handler uiHandler = UiThread.getHandler();
- mOrientationListener = new OrientationListener(mContext, uiHandler);
+ mOrientationListener =
+ new OrientationListener(mContext, uiHandler, defaultRotation);
mOrientationListener.setCurrentRotation(mRotation);
mSettingsObserver = new SettingsObserver(uiHandler);
mSettingsObserver.observe();
@@ -1735,8 +1737,9 @@
private class OrientationListener extends WindowOrientationListener implements Runnable {
transient boolean mEnabled;
- OrientationListener(Context context, Handler handler) {
- super(context, handler);
+ OrientationListener(Context context, Handler handler,
+ @Surface.Rotation int defaultRotation) {
+ super(context, handler, defaultRotation);
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java
index 3e165e4..14c816d 100644
--- a/services/core/java/com/android/server/wm/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java
@@ -88,14 +88,19 @@
private final Object mLock = new Object();
+ @Surface.Rotation
+ private final int mDefaultRotation;
+
/**
* Creates a new WindowOrientationListener.
*
* @param context for the WindowOrientationListener.
* @param handler Provides the Looper for receiving sensor updates.
+ * @param defaultRotation Default rotation of the display.
*/
- public WindowOrientationListener(Context context, Handler handler) {
- this(context, handler, SensorManager.SENSOR_DELAY_UI);
+ public WindowOrientationListener(Context context, Handler handler,
+ @Surface.Rotation int defaultRotation) {
+ this(context, handler, defaultRotation, SensorManager.SENSOR_DELAY_UI);
}
/**
@@ -103,7 +108,7 @@
*
* @param context for the WindowOrientationListener.
* @param handler Provides the Looper for receiving sensor updates.
- * @param wmService WindowManagerService to read the device config from.
+ * @param defaultRotation Default rotation of the display.
* @param rate at which sensor events are processed (see also
* {@link android.hardware.SensorManager SensorManager}). Use the default
* value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
@@ -111,10 +116,11 @@
*
* This constructor is private since no one uses it.
*/
- private WindowOrientationListener(
- Context context, Handler handler, int rate) {
+ private WindowOrientationListener(Context context, Handler handler,
+ @Surface.Rotation int defaultRotation, int rate) {
mContext = context;
mHandler = handler;
+ mDefaultRotation = defaultRotation;
mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
mRate = rate;
List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION);
@@ -1159,7 +1165,7 @@
"Reusing the last rotation resolution: " + mLastRotationResolution);
finalizeRotation(mLastRotationResolution);
} else {
- finalizeRotation(Surface.ROTATION_0);
+ finalizeRotation(mDefaultRotation);
}
return;
}
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 33ac735..ea0481e 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -43,6 +43,9 @@
<!-- needed by TrustManagerServiceTest to access LockSettings' secure storage -->
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
+ <!-- needed by GameManagerServiceTest because GameManager creates a UidObserver -->
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+
<application android:testOnly="true"
android:debuggable="true">
<uses-library android:name="android.test.runner" />
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index fa4a9de..2d5f0b0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -29,13 +29,16 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.Manifest;
+import android.app.ActivityManager;
import android.app.GameManager;
import android.app.GameModeInfo;
import android.app.GameState;
@@ -203,6 +206,23 @@
LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
}
+ private void mockAppCategory(String packageName, @ApplicationInfo.Category int category)
+ throws Exception {
+ reset(mMockPackageManager);
+ final ApplicationInfo gameApplicationInfo = new ApplicationInfo();
+ gameApplicationInfo.category = category;
+ gameApplicationInfo.packageName = packageName;
+ final PackageInfo pi = new PackageInfo();
+ pi.packageName = packageName;
+ pi.applicationInfo = gameApplicationInfo;
+ final List<PackageInfo> packages = new ArrayList<>();
+ packages.add(pi);
+ when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
+ .thenReturn(packages);
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(gameApplicationInfo);
+ }
+
@After
public void tearDown() throws Exception {
LocalServices.removeServiceForTest(PowerManagerInternal.class);
@@ -1597,4 +1617,113 @@
ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
ArgumentMatchers.eq(0.0f));
}
+
+ private GameManagerService createServiceAndStartUser(int userId) {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper());
+ startUser(gameManagerService, userId);
+ return gameManagerService;
+ }
+
+ @Test
+ public void testGamePowerMode_gamePackage() throws Exception {
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String[] packages = {mPackageName};
+ when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ }
+
+ @Test
+ public void testGamePowerMode_twoGames() throws Exception {
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String[] packages1 = {mPackageName};
+ when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
+ String someGamePkg = "some.game";
+ String[] packages2 = {someGamePkg};
+ int somePackageId = DEFAULT_PACKAGE_UID + 1;
+ when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ HashMap<Integer, Boolean> powerState = new HashMap<>();
+ doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
+ .when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ assertTrue(powerState.get(Mode.GAME));
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ assertTrue(powerState.get(Mode.GAME));
+ gameManagerService.mUidObserver.onUidStateChanged(
+ somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ assertFalse(powerState.get(Mode.GAME));
+ }
+
+ @Test
+ public void testGamePowerMode_twoGamesOverlap() throws Exception {
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String[] packages1 = {mPackageName};
+ when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
+ String someGamePkg = "some.game";
+ String[] packages2 = {someGamePkg};
+ int somePackageId = DEFAULT_PACKAGE_UID + 1;
+ when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ }
+
+ @Test
+ public void testGamePowerMode_released() throws Exception {
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String[] packages = {mPackageName};
+ when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ }
+
+ @Test
+ public void testGamePowerMode_noPackage() throws Exception {
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String[] packages = {};
+ when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
+ }
+
+ @Test
+ public void testGamePowerMode_notAGamePackage() throws Exception {
+ mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String[] packages = {"someapp"};
+ when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
+ }
+
+ @Test
+ public void testGamePowerMode_notAGamePackageNotReleased() throws Exception {
+ mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String[] packages = {"someapp"};
+ when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, false);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
new file mode 100644
index 0000000..6c73f71
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.dreams;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.service.dreams.DreamOverlayService;
+import android.service.dreams.IDreamOverlay;
+import android.service.dreams.IDreamOverlayCallback;
+import android.service.dreams.IDreamOverlayClient;
+import android.service.dreams.IDreamOverlayClientCallback;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * A collection of tests to exercise {@link DreamOverlayService}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DreamOverlayServiceTest {
+ private static final ComponentName FIRST_DREAM_COMPONENT =
+ ComponentName.unflattenFromString("com.foo.bar/.DreamService");
+ private static final ComponentName SECOND_DREAM_COMPONENT =
+ ComponentName.unflattenFromString("com.foo.baz/.DreamService");
+
+ @Mock
+ WindowManager.LayoutParams mLayoutParams;
+
+ @Mock
+ IDreamOverlayCallback mOverlayCallback;
+
+ /**
+ * {@link TestDreamOverlayService} is a simple {@link DreamOverlayService} implementation for
+ * tracking interactions across {@link IDreamOverlay} binder interface. The service reports
+ * interactions to a {@link Monitor} instance provided at construction.
+ */
+ private static class TestDreamOverlayService extends DreamOverlayService {
+ /**
+ * An interface implemented to be informed when the corresponding methods in
+ * {@link TestDreamOverlayService} are invoked.
+ */
+ interface Monitor {
+ void onStartDream();
+ void onEndDream();
+ void onWakeUp();
+ }
+
+ private final Monitor mMonitor;
+
+ TestDreamOverlayService(Monitor monitor) {
+ super();
+ mMonitor = monitor;
+ }
+
+ @Override
+ public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
+ mMonitor.onStartDream();
+ }
+
+ @Override
+ public void onEndDream() {
+ mMonitor.onEndDream();
+ super.onEndDream();
+ }
+
+ @Override
+ public void onWakeUp(@NonNull Runnable onCompleteCallback) {
+ mMonitor.onWakeUp();
+ super.onWakeUp(onCompleteCallback);
+ }
+ }
+
+ /**
+ * A {@link IDreamOverlayClientCallback} implementation that captures the requested client.
+ */
+ private static class OverlayClientCallback extends IDreamOverlayClientCallback.Stub {
+ public IDreamOverlayClient retrievedClient;
+ @Override
+ public void onDreamOverlayClient(IDreamOverlayClient client) throws RemoteException {
+ retrievedClient = client;
+ }
+ }
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Verifies that only the currently started dream is able to affect the overlay.
+ */
+ @Test
+ public void testOverlayClientInteraction() throws RemoteException {
+ final TestDreamOverlayService.Monitor monitor = Mockito.mock(
+ TestDreamOverlayService.Monitor.class);
+ final TestDreamOverlayService service = new TestDreamOverlayService(monitor);
+ final IBinder binder = service.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder);
+
+ // Create two overlay clients and ensure they are unique.
+ final IDreamOverlayClient firstClient = getClient(overlay);
+ assertThat(firstClient).isNotNull();
+
+ final IDreamOverlayClient secondClient = getClient(overlay);
+ assertThat(secondClient).isNotNull();
+
+ assertThat(firstClient).isNotEqualTo(secondClient);
+
+ // Start a dream with the first client and ensure the dream is now active from the
+ // overlay's perspective.
+ firstClient.startDream(mLayoutParams, mOverlayCallback,
+ FIRST_DREAM_COMPONENT.flattenToString(), false);
+
+
+ verify(monitor).onStartDream();
+ assertThat(service.getDreamComponent()).isEqualTo(FIRST_DREAM_COMPONENT);
+
+ Mockito.clearInvocations(monitor);
+
+ // Start a dream from the second client and verify that the overlay has both cycled to
+ // the new dream (ended/started).
+ secondClient.startDream(mLayoutParams, mOverlayCallback,
+ SECOND_DREAM_COMPONENT.flattenToString(), false);
+
+ verify(monitor).onEndDream();
+ verify(monitor).onStartDream();
+ assertThat(service.getDreamComponent()).isEqualTo(SECOND_DREAM_COMPONENT);
+
+ Mockito.clearInvocations(monitor);
+
+ // Verify that interactions with the first, now inactive client don't affect the overlay.
+ firstClient.endDream();
+ verify(monitor, never()).onEndDream();
+
+ firstClient.wakeUp();
+ verify(monitor, never()).onWakeUp();
+ }
+
+ private static IDreamOverlayClient getClient(IDreamOverlay overlay) throws RemoteException {
+ final OverlayClientCallback callback = new OverlayClientCallback();
+ overlay.getClient(callback);
+ return callback.retrievedClient;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
index 3de65c1..1b3a199 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
@@ -132,6 +132,19 @@
assertThat(mFinalizedRotation).isEqualTo(DEFAULT_SENSOR_ROTATION);
}
+ @Test
+ public void testOnSensorChanged_screenLocked_doNotCallRotationResolverReturnDefaultRotation() {
+ mWindowOrientationListener = new TestableWindowOrientationListener(mMockContext,
+ mHandler, /* defaultRotation */ Surface.ROTATION_180);
+ mWindowOrientationListener.mRotationResolverService = mFakeRotationResolverInternal;
+ mWindowOrientationListener.mIsScreenLocked = true;
+
+ mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent);
+
+ assertThat(mWindowOrientationListener.mIsOnProposedRotationChangedCalled).isFalse();
+ assertThat(mFinalizedRotation).isEqualTo(Surface.ROTATION_180);
+ }
+
static final class TestableRotationResolver extends RotationResolverInternal {
@Surface.Rotation
RotationResolverCallbackInternal mCallback;
@@ -166,21 +179,17 @@
}
}
- @Test
- public void testOnSensorChanged_inLockScreen_doNotCallRotationResolver() {
- mWindowOrientationListener.mIsScreenLocked = true;
-
- mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent);
-
- assertThat(mWindowOrientationListener.mIsOnProposedRotationChangedCalled).isFalse();
- }
-
final class TestableWindowOrientationListener extends WindowOrientationListener {
private boolean mIsOnProposedRotationChangedCalled = false;
private boolean mIsScreenLocked;
TestableWindowOrientationListener(Context context, Handler handler) {
- super(context, handler);
+ this(context, handler, Surface.ROTATION_0);
+ }
+
+ TestableWindowOrientationListener(Context context, Handler handler,
+ @Surface.Rotation int defaultRotation) {
+ super(context, handler, defaultRotation);
this.mOrientationJudge = new OrientationSensorJudge();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index a09bb33..0bcee92 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -2910,6 +2910,39 @@
}
@Test
+ public void testUpdateResolvedBoundsHorizontalPosition_leftInsets_appCentered() {
+ // Set up folded display
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1100, 2100)
+ .setCanRotate(true)
+ .build();
+ display.setIgnoreOrientationRequest(true);
+ final DisplayPolicy policy = display.getDisplayPolicy();
+ DisplayPolicy.DecorInsets.Info decorInfo = policy.getDecorInsetsInfo(ROTATION_90,
+ display.mBaseDisplayHeight, display.mBaseDisplayWidth);
+ decorInfo.mNonDecorInsets.set(130, 0, 60, 0);
+ spyOn(policy);
+ doReturn(decorInfo).when(policy).getDecorInsetsInfo(ROTATION_90,
+ display.mBaseDisplayHeight, display.mBaseDisplayWidth);
+ mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+
+ setUpApp(display);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ // Resize the display to simulate unfolding in portrait
+ resizeDisplay(mTask.mDisplayContent, 2200, 1800);
+ assertTrue(mActivity.inSizeCompatMode());
+
+ // Simulate real display not taking non-decor insets into consideration
+ display.getWindowConfiguration().setAppBounds(0, 0, 2200, 1800);
+
+ // Rotate display to landscape
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+ // App is centered
+ assertEquals(mActivity.getBounds(), new Rect(350, 50, 1450, 2150));
+ }
+
+ @Test
public void testUpdateResolvedBoundsHorizontalPosition_left() {
// Display configured as (2800, 1400).
assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
@@ -3055,6 +3088,20 @@
}
@Test
+ public void testApplyAspectRatio_containingRatioAlmostEqualToMaxRatio_boundsUnchanged() {
+ setUpDisplaySizeWithApp(1981, 2576);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+
+ final Rect originalBounds = new Rect(mActivity.getBounds());
+ prepareUnresizable(mActivity, 1.3f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+ // The containing aspect ratio is now 1.3003534, while the desired aspect ratio is 1.3. The
+ // bounds of the activity should not be changed as the difference is too small
+ assertEquals(mActivity.getBounds(), originalBounds);
+ }
+
+ @Test
public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() {
// When activity width equals parent width, multiplier shouldn't have any effect.
assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
@@ -3066,6 +3113,39 @@
}
@Test
+ public void testUpdateResolvedBoundsVerticalPosition_topInsets_appCentered() {
+ // Set up folded display
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2100, 1100)
+ .setCanRotate(true)
+ .build();
+ display.setIgnoreOrientationRequest(true);
+ final DisplayPolicy policy = display.getDisplayPolicy();
+ DisplayPolicy.DecorInsets.Info decorInfo = policy.getDecorInsetsInfo(ROTATION_90,
+ display.mBaseDisplayHeight, display.mBaseDisplayWidth);
+ decorInfo.mNonDecorInsets.set(0, 130, 0, 60);
+ spyOn(policy);
+ doReturn(decorInfo).when(policy).getDecorInsetsInfo(ROTATION_90,
+ display.mBaseDisplayHeight, display.mBaseDisplayWidth);
+ mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+
+ setUpApp(display);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+ // Resize the display to simulate unfolding in portrait
+ resizeDisplay(mTask.mDisplayContent, 1800, 2200);
+ assertTrue(mActivity.inSizeCompatMode());
+
+ // Simulate real display not taking non-decor insets into consideration
+ display.getWindowConfiguration().setAppBounds(0, 0, 1800, 2200);
+
+ // Rotate display to landscape
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+ // App is centered
+ assertEquals(mActivity.getBounds(), new Rect(50, 350, 2150, 1450));
+ }
+
+ @Test
public void testUpdateResolvedBoundsVerticalPosition_top() {
// Display configured as (1400, 2800).
assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(