Snap for 11623990 from 017af39acb to 24Q3-release
Change-Id: Iec649149bb819fd4b5bf598a3fe07f51c1ad8ce8
This commit is contained in:
+4
-23
@@ -17,7 +17,7 @@ package {
|
||||
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||
}
|
||||
|
||||
min_launcher3_sdk_version = "26"
|
||||
min_launcher3_sdk_version = "30"
|
||||
|
||||
// Common source files used to build launcher (java and kotlin)
|
||||
// All sources are split so they can be reused in many other libraries/apps in other folders
|
||||
@@ -69,22 +69,6 @@ filegroup {
|
||||
],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher-ext_tests",
|
||||
srcs: [
|
||||
"ext_tests/**/*.java",
|
||||
"ext_tests/**/*.kt",
|
||||
],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "launcher-quickstep-ext_tests",
|
||||
srcs: [
|
||||
"quickstep/ext_tests/**/*.java",
|
||||
"quickstep/ext_tests/**/*.kt",
|
||||
],
|
||||
}
|
||||
|
||||
// Proguard files for Launcher3
|
||||
filegroup {
|
||||
name: "launcher-proguard-rules",
|
||||
@@ -186,7 +170,7 @@ android_library {
|
||||
sdk_version: "current",
|
||||
min_sdk_version: min_launcher3_sdk_version,
|
||||
lint: {
|
||||
baseline_filename: "lint-baseline2.xml",
|
||||
baseline_filename: "lint-baseline.xml",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -212,7 +196,7 @@ android_library {
|
||||
min_sdk_version: min_launcher3_sdk_version,
|
||||
manifest: "AndroidManifest-common.xml",
|
||||
lint: {
|
||||
baseline_filename: "lint-baseline2.xml",
|
||||
baseline_filename: "lint-baseline.xml",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -229,11 +213,8 @@ android_app {
|
||||
":launcher-src",
|
||||
":launcher-src_shortcuts_overrides",
|
||||
":launcher-src_ui_overrides",
|
||||
":launcher-ext_tests",
|
||||
],
|
||||
resource_dirs: [
|
||||
"ext_tests/res",
|
||||
],
|
||||
|
||||
optimize: {
|
||||
proguard_flags_files: ["proguard.flags"],
|
||||
// Proguard is disable for testing. Derivarive prjects to keep proguard enabled
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="test_information_handler_class" translatable="false">com.android.launcher3.testing.DebugTestInformationHandler</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.testing;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.system.Os;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.ShortcutAndWidgetContainer;
|
||||
import com.android.launcher3.icons.ClockDrawableWrapper;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Class to handle requests from tests, including debug ones.
|
||||
*/
|
||||
public class DebugTestInformationHandler extends TestInformationHandler {
|
||||
private static Collection<String> sEvents;
|
||||
private static Application.ActivityLifecycleCallbacks sActivityLifecycleCallbacks;
|
||||
private static final Map<Activity, Boolean> sActivities =
|
||||
Collections.synchronizedMap(new WeakHashMap<>());
|
||||
private static int sActivitiesCreatedCount = 0;
|
||||
|
||||
public DebugTestInformationHandler(Context context) {
|
||||
init(context);
|
||||
if (sActivityLifecycleCallbacks == null) {
|
||||
sActivityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {
|
||||
@Override
|
||||
public void onActivityCreated(Activity activity, Bundle bundle) {
|
||||
sActivities.put(activity, true);
|
||||
++sActivitiesCreatedCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStarted(Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResumed(Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPaused(Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped(Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity activity) {
|
||||
}
|
||||
};
|
||||
((Application) context.getApplicationContext())
|
||||
.registerActivityLifecycleCallbacks(sActivityLifecycleCallbacks);
|
||||
}
|
||||
}
|
||||
|
||||
private static void runGcAndFinalizersSync() {
|
||||
Runtime.getRuntime().gc();
|
||||
Runtime.getRuntime().runFinalization();
|
||||
|
||||
final CountDownLatch fence = new CountDownLatch(1);
|
||||
createFinalizationObserver(fence);
|
||||
try {
|
||||
do {
|
||||
Runtime.getRuntime().gc();
|
||||
Runtime.getRuntime().runFinalization();
|
||||
} while (!fence.await(100, TimeUnit.MILLISECONDS));
|
||||
} catch (InterruptedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the observer in the scope of a method to minimize the chance that
|
||||
// it remains live in a DEX/machine register at the point of the fence guard.
|
||||
// This must be kept to avoid R8 inlining it.
|
||||
@Keep
|
||||
private static void createFinalizationObserver(CountDownLatch fence) {
|
||||
new Object() {
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
fence.countDown();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle call(String method, String arg, @Nullable Bundle extras) {
|
||||
final Bundle response = new Bundle();
|
||||
switch (method) {
|
||||
case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
|
||||
return getLauncherUIProperty(Bundle::putInt,
|
||||
l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
|
||||
TestProtocol.sDebugTracing = true;
|
||||
ClockDrawableWrapper.sRunningInTest = true;
|
||||
return response;
|
||||
|
||||
case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
|
||||
TestProtocol.sDebugTracing = false;
|
||||
ClockDrawableWrapper.sRunningInTest = false;
|
||||
return response;
|
||||
|
||||
case TestProtocol.REQUEST_PID: {
|
||||
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, Os.getpid());
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_FORCE_GC: {
|
||||
runGcAndFinalizersSync();
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_START_EVENT_LOGGING: {
|
||||
sEvents = new ArrayList<>();
|
||||
TestLogging.setEventConsumer(
|
||||
(sequence, event) -> {
|
||||
final Collection<String> events = sEvents;
|
||||
if (events != null) {
|
||||
synchronized (events) {
|
||||
events.add(sequence + '/' + event);
|
||||
}
|
||||
}
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_STOP_EVENT_LOGGING: {
|
||||
TestLogging.setEventConsumer(null);
|
||||
sEvents = null;
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_GET_TEST_EVENTS: {
|
||||
if (sEvents == null) {
|
||||
// sEvents can be null if Launcher died and restarted after
|
||||
// REQUEST_START_EVENT_LOGGING.
|
||||
return response;
|
||||
}
|
||||
|
||||
synchronized (sEvents) {
|
||||
response.putStringArrayList(
|
||||
TestProtocol.TEST_INFO_RESPONSE_FIELD, new ArrayList<>(sEvents));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_REINITIALIZE_DATA: {
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
|
||||
model.getModelDbController().createEmptyDB();
|
||||
MAIN_EXECUTOR.execute(model::forceReload);
|
||||
});
|
||||
return response;
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_CLEAR_DATA: {
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
|
||||
model.getModelDbController().createEmptyDB();
|
||||
model.getModelDbController().clearEmptyDbFlag();
|
||||
MAIN_EXECUTOR.execute(model::forceReload);
|
||||
});
|
||||
return response;
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_HOTSEAT_ICON_NAMES: {
|
||||
return getLauncherUIProperty(Bundle::putStringArrayList, l -> {
|
||||
ShortcutAndWidgetContainer hotseatIconsContainer =
|
||||
l.getHotseat().getShortcutsAndWidgets();
|
||||
ArrayList<String> hotseatIconNames = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < hotseatIconsContainer.getChildCount(); i++) {
|
||||
// Use unchecked cast to catch changes in hotseat layout
|
||||
BubbleTextView icon = (BubbleTextView) hotseatIconsContainer.getChildAt(i);
|
||||
hotseatIconNames.add((String) icon.getText());
|
||||
}
|
||||
|
||||
return hotseatIconNames;
|
||||
});
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT: {
|
||||
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, sActivitiesCreatedCount);
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_GET_ACTIVITIES: {
|
||||
response.putStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD,
|
||||
sActivities.keySet().stream().map(
|
||||
a -> a.getClass().getSimpleName() + " ("
|
||||
+ (a.isDestroyed() ? "destroyed" : "current") + ")")
|
||||
.toArray(String[]::new));
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_MODEL_QUEUE_CLEARED:
|
||||
return getFromExecutorSync(MODEL_EXECUTOR, Bundle::new);
|
||||
|
||||
default:
|
||||
return super.call(method, arg, extras);
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-138
@@ -3,13 +3,13 @@
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 29 (current min is 26): `android.content.res.Resources#getFloat`"
|
||||
errorLine1=" return mContext.getResources().getFloat(resId);"
|
||||
errorLine2=" ~~~~~~~~">
|
||||
message="`@android:dimen/system_app_widget_background_radius` requires API level 31 (current min is 26)"
|
||||
errorLine1=' <corners android:radius="@android:dimen/system_app_widget_background_radius" />'
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/util/DynamicResource.java"
|
||||
line="73"
|
||||
column="40"/>
|
||||
file="packages/apps/Launcher3/res/drawable/widget_resize_frame.xml"
|
||||
line="20"
|
||||
column="14"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
@@ -34,136 +34,4 @@
|
||||
column="18"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 28 (current min is 26): `android.app.Person#getKey`"
|
||||
errorLine1=" return people.stream().filter(person -> person.getKey() != null)"
|
||||
errorLine2=" ~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/notification/NotificationKeyData.java"
|
||||
line="72"
|
||||
column="56"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Method reference requires API level 28 (current min is 26): `Person::getKey`"
|
||||
errorLine1=" .map(Person::getKey).sorted().toArray(String[]::new);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/notification/NotificationKeyData.java"
|
||||
line="73"
|
||||
column="22"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_LEFT`"
|
||||
errorLine1=" AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
|
||||
line="1814"
|
||||
column="17"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_RIGHT`"
|
||||
errorLine1=" : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
|
||||
line="1815"
|
||||
column="19"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_RIGHT`"
|
||||
errorLine1=" AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
|
||||
line="1823"
|
||||
column="17"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Field requires API level 29 (current min is 26): `android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_PAGE_LEFT`"
|
||||
errorLine1=" : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/PagedView.java"
|
||||
line="1824"
|
||||
column="19"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 30 (current min is 26): `android.graphics.Outline#setPath`"
|
||||
errorLine1=" outline.setPath(mPath);"
|
||||
errorLine2=" ~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/popup/RoundedArrowDrawable.java"
|
||||
line="114"
|
||||
column="17"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Field requires API level 28 (current min is 26): `android.appwidget.AppWidgetProviderInfo#widgetFeatures`"
|
||||
errorLine1=" int featureFlags = mProviderInfo.widgetFeatures;"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/widget/WidgetAddFlowHandler.java"
|
||||
line="93"
|
||||
column="28"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 30 (current min is 26): `android.view.View#getWindowInsetsController`"
|
||||
errorLine1=" WindowInsetsController insetsController = getWindowInsetsController();"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
|
||||
line="820"
|
||||
column="51"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 30 (current min is 26): `android.view.WindowInsets.Type#ime`"
|
||||
errorLine1=" insetsController.hide(WindowInsets.Type.ime());"
|
||||
errorLine2=" ~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
|
||||
line="822"
|
||||
column="53"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Call requires API level 30 (current min is 26): `android.view.WindowInsetsController#hide`"
|
||||
errorLine1=" insetsController.hide(WindowInsets.Type.ime());"
|
||||
errorLine2=" ~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java"
|
||||
line="822"
|
||||
column="30"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="Method reference requires API level 28 (current min is 26): `Person::getKey`"
|
||||
errorLine1=" : Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/src/com/android/launcher3/model/data/WorkspaceItemInfo.java"
|
||||
line="194"
|
||||
column="42"/>
|
||||
</issue>
|
||||
|
||||
</issues>
|
||||
@@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)"
|
||||
errorLine1=' android:topLeftRadius="?android:attr/dialogCornerRadius"'
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
|
||||
line="6"
|
||||
column="9"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="`?android:attr/dialogCornerRadius` requires API level 28 (current min is 26)"
|
||||
errorLine1=' android:topRightRadius="?android:attr/dialogCornerRadius" />'
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/res/drawable/add_item_dialog_background.xml"
|
||||
line="7"
|
||||
column="9"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="NewApi"
|
||||
message="`@android:dimen/system_app_widget_background_radius` requires API level 31 (current min is 26)"
|
||||
errorLine1=' <corners android:radius="@android:dimen/system_app_widget_background_radius" />'
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="packages/apps/Launcher3/res/drawable/widget_resize_frame.xml"
|
||||
line="20"
|
||||
column="14"/>
|
||||
</issue>
|
||||
|
||||
</issues>
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.testing.DebugTestInformationHandler;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
|
||||
/**
|
||||
* Class to handle requests from tests, including debug ones, to Quickstep Launcher builds.
|
||||
*/
|
||||
public abstract class DebugQuickstepTestInformationHandler extends QuickstepTestInformationHandler {
|
||||
|
||||
private final DebugTestInformationHandler mDebugTestInformationHandler;
|
||||
|
||||
public DebugQuickstepTestInformationHandler(Context context) {
|
||||
super(context);
|
||||
mDebugTestInformationHandler = new DebugTestInformationHandler(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle call(String method, String arg, @Nullable Bundle extras) {
|
||||
Bundle response = new Bundle();
|
||||
if (TestProtocol.REQUEST_RECREATE_TASKBAR.equals(method)) {
|
||||
// Allow null-pointer to catch illegal states.
|
||||
runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbar());
|
||||
return response;
|
||||
}
|
||||
response = super.call(method, arg, extras);
|
||||
if (response != null) return response;
|
||||
return mDebugTestInformationHandler.call(method, arg, extras);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2024 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.
|
||||
-->
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@android:color/white" />
|
||||
<corners android:radius="@dimen/keyboard_quick_switch_task_view_radius" />
|
||||
</shape>
|
||||
@@ -18,6 +18,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:background="@drawable/keyboard_quick_switch_task_view_background"
|
||||
android:background="@drawable/keyboard_quick_switch_thumbnail_background"
|
||||
android:clipToOutline="true"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
@@ -97,7 +97,11 @@ public final class KeyboardQuickSwitchController implements
|
||||
|
||||
private void openQuickSwitchView(int currentFocusedIndex) {
|
||||
if (mQuickSwitchViewController != null) {
|
||||
return;
|
||||
if (!mQuickSwitchViewController.isCloseAnimationRunning()) {
|
||||
return;
|
||||
}
|
||||
// Allow the KQS to be reopened during the close animation to make it more responsive
|
||||
closeQuickSwitchView(false);
|
||||
}
|
||||
TaskbarOverlayContext overlayContext =
|
||||
mControllers.taskbarOverlayController.requestWindow();
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
@@ -174,21 +175,23 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
|
||||
return;
|
||||
}
|
||||
if (updateFunction == null) {
|
||||
applyThumbnail(thumbnailView, task.thumbnail);
|
||||
applyThumbnail(thumbnailView, task.colorBackground, task.thumbnail);
|
||||
return;
|
||||
}
|
||||
updateFunction.updateThumbnailInBackground(
|
||||
task, thumbnailData -> applyThumbnail(thumbnailView, thumbnailData));
|
||||
updateFunction.updateThumbnailInBackground(task, thumbnailData ->
|
||||
applyThumbnail(thumbnailView, task.colorBackground, thumbnailData));
|
||||
}
|
||||
|
||||
private void applyThumbnail(
|
||||
@NonNull ImageView thumbnailView,
|
||||
ThumbnailData thumbnailData) {
|
||||
@ColorInt int backgroundColor,
|
||||
@Nullable ThumbnailData thumbnailData) {
|
||||
Bitmap bm = thumbnailData == null ? null : thumbnailData.thumbnail;
|
||||
|
||||
if (thumbnailView.getVisibility() != VISIBLE) {
|
||||
thumbnailView.setVisibility(VISIBLE);
|
||||
}
|
||||
thumbnailView.getBackground().setTint(bm == null ? backgroundColor : Color.TRANSPARENT);
|
||||
thumbnailView.setImageDrawable(new BlurredBitmapDrawable(bm, THUMBNAIL_BLUR_RADIUS));
|
||||
}
|
||||
|
||||
|
||||
@@ -94,8 +94,12 @@ public class KeyboardQuickSwitchViewController {
|
||||
mViewCallbacks);
|
||||
}
|
||||
|
||||
boolean isCloseAnimationRunning() {
|
||||
return mCloseAnimation != null;
|
||||
}
|
||||
|
||||
protected void closeQuickSwitchView(boolean animate) {
|
||||
if (mCloseAnimation != null) {
|
||||
if (isCloseAnimationRunning()) {
|
||||
// Let currently-running animation finish.
|
||||
if (!animate) {
|
||||
mCloseAnimation.end();
|
||||
@@ -130,7 +134,7 @@ public class KeyboardQuickSwitchViewController {
|
||||
}
|
||||
|
||||
private int launchTaskAt(int index) {
|
||||
if (mCloseAnimation != null) {
|
||||
if (isCloseAnimationRunning()) {
|
||||
// Ignore taps on task views and alt key unpresses while the close animation is running.
|
||||
return -1;
|
||||
}
|
||||
@@ -138,7 +142,7 @@ public class KeyboardQuickSwitchViewController {
|
||||
// views have been added in the KeyboardQuickSwitchView.
|
||||
GroupTask task = mControllerCallbacks.getTaskAt(index);
|
||||
if (task == null) {
|
||||
return Math.max(0, index);
|
||||
return mOnDesktop ? 1 : Math.max(0, index);
|
||||
}
|
||||
if (mControllerCallbacks.isTaskRunning(task)) {
|
||||
// Ignore attempts to run the selected task if it is already running.
|
||||
@@ -186,7 +190,7 @@ public class KeyboardQuickSwitchViewController {
|
||||
pw.println(prefix + "KeyboardQuickSwitchViewController:");
|
||||
|
||||
pw.println(prefix + "\thasFocus=" + mKeyboardQuickSwitchView.hasFocus());
|
||||
pw.println(prefix + "\tcloseAnimationRunning=" + (mCloseAnimation != null));
|
||||
pw.println(prefix + "\tisCloseAnimationRunning=" + isCloseAnimationRunning());
|
||||
pw.println(prefix + "\tmCurrentFocusIndex=" + mCurrentFocusIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,14 +37,12 @@ import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
|
||||
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING;
|
||||
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_FULLSCREEN;
|
||||
import static com.android.launcher3.taskbar.TaskbarDragLayerController.TASKBAR_REAPPEAR_DELAY_MS;
|
||||
import static com.android.launcher3.testing.shared.ResourceUtils.getBoolByName;
|
||||
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.ActivityNotFoundException;
|
||||
@@ -82,7 +80,6 @@ import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.apppairs.AppPairIcon;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
@@ -1432,23 +1429,6 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
});
|
||||
}
|
||||
|
||||
public void hideTaskbarWhenFolding() {
|
||||
AnimatedFloat alphaAnim = mControllers.taskbarDragLayerController.getTaskbarAlpha();
|
||||
alphaAnim.cancelAnimation();
|
||||
alphaAnim.updateValue(0);
|
||||
ObjectAnimator animator = alphaAnim.animateToValue(1).setDuration(0);
|
||||
animator.setStartDelay(TASKBAR_REAPPEAR_DELAY_MS);
|
||||
animator.start();
|
||||
}
|
||||
|
||||
public void cancelHideTaskbarWhenFolding() {
|
||||
mControllers.taskbarDragLayerController.getTaskbarAlpha().cancelAnimation();
|
||||
}
|
||||
|
||||
public void resetHideTaskbarWhenUnfolding() {
|
||||
mControllers.taskbarDragLayerController.getTaskbarAlpha().updateValue(1);
|
||||
}
|
||||
|
||||
protected boolean isUserSetupComplete() {
|
||||
return mIsUserSetupComplete;
|
||||
}
|
||||
|
||||
@@ -44,12 +44,6 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
private static final boolean DEBUG = SystemProperties.getBoolean(
|
||||
"persist.debug.draw_taskbar_debug_ui", false);
|
||||
|
||||
// Delay to reset the task bar alpha back to 1 after fading it for transition from unfold to
|
||||
// fold. Normally this is not needed since the new task bar is recreated after fading, but in
|
||||
// case something goes wrong this provides a fallback mechanism to make sure the task bar is
|
||||
// visible after the transition finishes.
|
||||
public static final long TASKBAR_REAPPEAR_DELAY_MS = 2000;
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final TaskbarDragLayer mTaskbarDragLayer;
|
||||
private final int mFolderMargin;
|
||||
|
||||
@@ -29,7 +29,6 @@ import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
|
||||
import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
|
||||
import static com.android.quickstep.util.SystemActionConstants.ACTION_SHOW_TASKBAR;
|
||||
@@ -43,7 +42,6 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.hardware.devicestate.DeviceStateManager;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
@@ -111,7 +109,6 @@ public class TaskbarManager {
|
||||
|
||||
private final Context mContext;
|
||||
private final @Nullable Context mNavigationBarPanelContext;
|
||||
private final DeviceStateManager mDeviceStateManager;
|
||||
private WindowManager mWindowManager;
|
||||
private FrameLayout mTaskbarRootLayout;
|
||||
private boolean mAddedWindow;
|
||||
@@ -180,8 +177,7 @@ public class TaskbarManager {
|
||||
}
|
||||
};
|
||||
|
||||
private final UnfoldTransitionProgressProvider.TransitionProgressListener
|
||||
mUnfoldTransitionProgressListener =
|
||||
UnfoldTransitionProgressProvider.TransitionProgressListener mUnfoldTransitionProgressListener =
|
||||
new UnfoldTransitionProgressProvider.TransitionProgressListener() {
|
||||
@Override
|
||||
public void onTransitionStarted() {
|
||||
@@ -210,9 +206,6 @@ public class TaskbarManager {
|
||||
}
|
||||
};
|
||||
|
||||
private final DeviceStateManager.FoldStateListener mFoldStateListener;
|
||||
private Boolean mFolded;
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
public TaskbarManager(
|
||||
TouchInteractionService service, AllAppsActionManager allAppsActionManager) {
|
||||
@@ -240,29 +233,6 @@ public class TaskbarManager {
|
||||
}
|
||||
};
|
||||
}
|
||||
// Temporary solution to mitigate the visual jump from folding the device. Currently, the
|
||||
// screen turns on much earlier than we receive the onConfigurationChanged callback or
|
||||
// receiving the correct device profile. While the ideal the solution is to align turning
|
||||
// the screen on after onConfigurationChanged (by either delaying turning on the screen or
|
||||
// figuring out what is causing the delay in getting onConfigurationChanged callback), one
|
||||
// easy temporary mitigation is to dimming the bar so that the visual jump isn't as glaring.
|
||||
mFoldStateListener = new DeviceStateManager.FoldStateListener(mContext, folded -> {
|
||||
boolean firstTime = mFolded == null;
|
||||
if (mTaskbarActivityContext == null) {
|
||||
return;
|
||||
}
|
||||
if (!firstTime && mFolded.booleanValue() != folded) {
|
||||
mTaskbarActivityContext.cancelHideTaskbarWhenFolding();
|
||||
}
|
||||
mFolded = folded;
|
||||
if (folded && !firstTime) {
|
||||
mTaskbarActivityContext.hideTaskbarWhenFolding();
|
||||
} else {
|
||||
mTaskbarActivityContext.resetHideTaskbarWhenUnfolding();
|
||||
}
|
||||
});
|
||||
mDeviceStateManager = mContext.getSystemService(DeviceStateManager.class);
|
||||
mDeviceStateManager.registerCallback(MAIN_EXECUTOR, mFoldStateListener);
|
||||
mNavButtonController = new TaskbarNavButtonController(service,
|
||||
SystemUiProxy.INSTANCE.get(mContext), new Handler(),
|
||||
AssistUtils.newInstance(mContext));
|
||||
@@ -619,7 +589,6 @@ public class TaskbarManager {
|
||||
Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
|
||||
mContext.unregisterComponentCallbacks(mComponentCallbacks);
|
||||
mContext.unregisterReceiver(mShutdownReceiver);
|
||||
mDeviceStateManager.unregisterCallback(mFoldStateListener);
|
||||
}
|
||||
|
||||
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
|
||||
|
||||
@@ -22,6 +22,7 @@ import static androidx.test.InstrumentationRegistry.getInstrumentation;
|
||||
import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
|
||||
import static com.android.launcher3.tapl.TestHelpers.getHomeIntentInPackage;
|
||||
import static com.android.launcher3.tapl.TestHelpers.getLauncherInMyProcess;
|
||||
import static com.android.launcher3.testing.shared.TestProtocol.UPDATE_OVERVIEW_TARGETS_RUNNING_LATE;
|
||||
import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_ACTIVITY_TIMEOUT;
|
||||
import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_BROADCAST_TIMEOUT_SECS;
|
||||
import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT;
|
||||
@@ -44,6 +45,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
@@ -59,6 +61,7 @@ import com.android.launcher3.tapl.TestHelpers;
|
||||
import com.android.launcher3.testcomponent.TestCommandReceiver;
|
||||
import com.android.launcher3.ui.AbstractLauncherUiTest;
|
||||
import com.android.launcher3.util.Wait;
|
||||
import com.android.launcher3.util.rule.ExtendedLongPressTimeoutRule;
|
||||
import com.android.launcher3.util.rule.FailureWatcher;
|
||||
import com.android.launcher3.util.rule.SamplerRule;
|
||||
import com.android.launcher3.util.rule.ScreenRecordRule;
|
||||
@@ -105,6 +108,9 @@ public class FallbackRecentsTest {
|
||||
@Rule
|
||||
public ScreenRecordRule mScreenRecordRule = new ScreenRecordRule();
|
||||
|
||||
@Rule
|
||||
public ExtendedLongPressTimeoutRule mLongPressTimeoutRule = new ExtendedLongPressTimeoutRule();
|
||||
|
||||
public FallbackRecentsTest() throws RemoteException {
|
||||
Instrumentation instrumentation = getInstrumentation();
|
||||
Context context = instrumentation.getContext();
|
||||
@@ -129,6 +135,13 @@ public class FallbackRecentsTest {
|
||||
getLauncherCommand(mOtherLauncherActivity));
|
||||
updateHandler.mChangeCounter
|
||||
.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS);
|
||||
Log.d(UPDATE_OVERVIEW_TARGETS_RUNNING_LATE,
|
||||
"AFTER AWAIT: mObserver home intent package name="
|
||||
+ updateHandler.mObserver.getHomeIntent()
|
||||
.getComponent().getPackageName());
|
||||
Log.d(UPDATE_OVERVIEW_TARGETS_RUNNING_LATE,
|
||||
"AFTER AWAIT: mOtherLauncherActivity package name="
|
||||
+ mOtherLauncherActivity.packageName);
|
||||
try {
|
||||
base.evaluate();
|
||||
} finally {
|
||||
@@ -340,12 +353,25 @@ public class FallbackRecentsTest {
|
||||
mRads = new RecentsAnimationDeviceState(ctx);
|
||||
mObserver = new OverviewComponentObserver(ctx, mRads);
|
||||
mChangeCounter = new CountDownLatch(1);
|
||||
Log.d(UPDATE_OVERVIEW_TARGETS_RUNNING_LATE,
|
||||
"OverviewUpdateHandler(Constructor): mObserver home intent package name="
|
||||
+ mObserver.getHomeIntent().getComponent().getPackageName());
|
||||
Log.d(UPDATE_OVERVIEW_TARGETS_RUNNING_LATE,
|
||||
"OverviewUpdateHandler(Constructor): mOtherLauncherActivity package name="
|
||||
+ mOtherLauncherActivity.packageName);
|
||||
if (mObserver.getHomeIntent().getComponent()
|
||||
.getPackageName().equals(mOtherLauncherActivity.packageName)) {
|
||||
// Home already same
|
||||
mChangeCounter.countDown();
|
||||
} else {
|
||||
mObserver.setOverviewChangeListener(b -> mChangeCounter.countDown());
|
||||
mObserver.setOverviewChangeListener(b -> {
|
||||
Log.d(UPDATE_OVERVIEW_TARGETS_RUNNING_LATE,
|
||||
"OverviewChangeListener(Callback): isHomeAndOverviewSame=" + b);
|
||||
Log.d(UPDATE_OVERVIEW_TARGETS_RUNNING_LATE,
|
||||
"OverviewChangeListener(Callback): mObserver home intent package name="
|
||||
+ mObserver.getHomeIntent().getComponent().getPackageName());
|
||||
mChangeCounter.countDown();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,39 +23,56 @@ import static com.android.launcher3.config.FeatureFlags.enableAppPairs;
|
||||
import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
|
||||
import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.system.Os;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.CellLayout;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Hotseat;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.ShortcutAndWidgetContainer;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.dragndrop.DragLayer;
|
||||
import com.android.launcher3.icons.ClockDrawableWrapper;
|
||||
import com.android.launcher3.testing.shared.HotseatCellCenterRequest;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.testing.shared.WorkspaceCellCenterRequest;
|
||||
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.ResourceBasedOverride;
|
||||
import com.android.launcher3.widget.picker.WidgetsFullSheet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -69,6 +86,12 @@ public class TestInformationHandler implements ResourceBasedOverride {
|
||||
context, R.string.test_information_handler_class);
|
||||
}
|
||||
|
||||
private static Collection<String> sEvents;
|
||||
private static Application.ActivityLifecycleCallbacks sActivityLifecycleCallbacks;
|
||||
private static final Set<Activity> sActivities =
|
||||
Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
|
||||
private static int sActivitiesCreatedCount = 0;
|
||||
|
||||
protected Context mContext;
|
||||
protected DeviceProfile mDeviceProfile;
|
||||
protected LauncherAppState mLauncherAppState;
|
||||
@@ -78,6 +101,17 @@ public class TestInformationHandler implements ResourceBasedOverride {
|
||||
mDeviceProfile = InvariantDeviceProfile.INSTANCE.
|
||||
get(context).getDeviceProfile(context);
|
||||
mLauncherAppState = LauncherAppState.getInstanceNoCreate();
|
||||
if (sActivityLifecycleCallbacks == null) {
|
||||
sActivityLifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
|
||||
@Override
|
||||
public void onActivityCreated(Activity activity, Bundle bundle) {
|
||||
sActivities.add(activity);
|
||||
++sActivitiesCreatedCount;
|
||||
}
|
||||
};
|
||||
((Application) context.getApplicationContext())
|
||||
.registerActivityLifecycleCallbacks(sActivityLifecycleCallbacks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,6 +343,127 @@ public class TestInformationHandler implements ResourceBasedOverride {
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
|
||||
return getLauncherUIProperty(Bundle::putInt,
|
||||
l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
|
||||
TestProtocol.sDebugTracing = true;
|
||||
ClockDrawableWrapper.sRunningInTest = true;
|
||||
return response;
|
||||
|
||||
case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
|
||||
TestProtocol.sDebugTracing = false;
|
||||
ClockDrawableWrapper.sRunningInTest = false;
|
||||
return response;
|
||||
|
||||
case TestProtocol.REQUEST_PID: {
|
||||
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, Os.getpid());
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_FORCE_GC: {
|
||||
runGcAndFinalizersSync();
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_START_EVENT_LOGGING: {
|
||||
sEvents = new ArrayList<>();
|
||||
TestLogging.setEventConsumer(
|
||||
(sequence, event) -> {
|
||||
final Collection<String> events = sEvents;
|
||||
if (events != null) {
|
||||
synchronized (events) {
|
||||
events.add(sequence + '/' + event);
|
||||
}
|
||||
}
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_STOP_EVENT_LOGGING: {
|
||||
TestLogging.setEventConsumer(null);
|
||||
sEvents = null;
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_GET_TEST_EVENTS: {
|
||||
if (sEvents == null) {
|
||||
// sEvents can be null if Launcher died and restarted after
|
||||
// REQUEST_START_EVENT_LOGGING.
|
||||
return response;
|
||||
}
|
||||
|
||||
synchronized (sEvents) {
|
||||
response.putStringArrayList(
|
||||
TestProtocol.TEST_INFO_RESPONSE_FIELD, new ArrayList<>(sEvents));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_REINITIALIZE_DATA: {
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
|
||||
model.getModelDbController().createEmptyDB();
|
||||
MAIN_EXECUTOR.execute(model::forceReload);
|
||||
});
|
||||
return response;
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_CLEAR_DATA: {
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
|
||||
model.getModelDbController().createEmptyDB();
|
||||
model.getModelDbController().clearEmptyDbFlag();
|
||||
MAIN_EXECUTOR.execute(model::forceReload);
|
||||
});
|
||||
return response;
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_HOTSEAT_ICON_NAMES: {
|
||||
return getLauncherUIProperty(Bundle::putStringArrayList, l -> {
|
||||
ShortcutAndWidgetContainer hotseatIconsContainer =
|
||||
l.getHotseat().getShortcutsAndWidgets();
|
||||
ArrayList<String> hotseatIconNames = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < hotseatIconsContainer.getChildCount(); i++) {
|
||||
// Use unchecked cast to catch changes in hotseat layout
|
||||
BubbleTextView icon = (BubbleTextView) hotseatIconsContainer.getChildAt(i);
|
||||
hotseatIconNames.add((String) icon.getText());
|
||||
}
|
||||
|
||||
return hotseatIconNames;
|
||||
});
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_GET_ACTIVITIES_CREATED_COUNT: {
|
||||
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, sActivitiesCreatedCount);
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_GET_ACTIVITIES: {
|
||||
response.putStringArray(TestProtocol.TEST_INFO_RESPONSE_FIELD,
|
||||
sActivities.stream().map(
|
||||
a -> a.getClass().getSimpleName() + " ("
|
||||
+ (a.isDestroyed() ? "destroyed" : "current") + ")")
|
||||
.toArray(String[]::new));
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_MODEL_QUEUE_CLEARED:
|
||||
return getFromExecutorSync(MODEL_EXECUTOR, Bundle::new);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -387,4 +542,38 @@ public class TestInformationHandler implements ResourceBasedOverride {
|
||||
*/
|
||||
void set(Bundle b, String key, T value);
|
||||
}
|
||||
|
||||
|
||||
private static void runGcAndFinalizersSync() {
|
||||
Runtime.getRuntime().gc();
|
||||
Runtime.getRuntime().runFinalization();
|
||||
|
||||
final CountDownLatch fence = new CountDownLatch(1);
|
||||
createFinalizationObserver(fence);
|
||||
try {
|
||||
do {
|
||||
Runtime.getRuntime().gc();
|
||||
Runtime.getRuntime().runFinalization();
|
||||
} while (!fence.await(100, TimeUnit.MILLISECONDS));
|
||||
} catch (InterruptedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the observer in the scope of a method to minimize the chance that
|
||||
// it remains live in a DEX/machine register at the point of the fence guard.
|
||||
// This must be kept to avoid R8 inlining it.
|
||||
@Keep
|
||||
private static void createFinalizationObserver(CountDownLatch fence) {
|
||||
new Object() {
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
fence.countDown();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +180,7 @@ public final class TestProtocol {
|
||||
public static final String TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE = "b/326073471";
|
||||
public static final String WIDGET_CONFIG_NULL_EXTRA_INTENT = "b/324419890";
|
||||
public static final String ACTIVITY_NOT_RESUMED_AFTER_BACK = "b/322823209";
|
||||
public static final String UPDATE_OVERVIEW_TARGETS_RUNNING_LATE = "b/321775748";
|
||||
|
||||
public static final String REQUEST_EMULATE_DISPLAY = "emulate-display";
|
||||
public static final String REQUEST_STOP_EMULATE_DISPLAY = "stop-emulate-display";
|
||||
|
||||
@@ -66,6 +66,7 @@ import com.android.launcher3.util.LooperExecutor;
|
||||
import com.android.launcher3.util.SimpleBroadcastReceiver;
|
||||
import com.android.launcher3.util.TestUtil;
|
||||
import com.android.launcher3.util.Wait;
|
||||
import com.android.launcher3.util.rule.ExtendedLongPressTimeoutRule;
|
||||
import com.android.launcher3.util.rule.FailureWatcher;
|
||||
import com.android.launcher3.util.rule.SamplerRule;
|
||||
import com.android.launcher3.util.rule.ScreenRecordRule;
|
||||
@@ -219,6 +220,9 @@ public abstract class AbstractLauncherUiTest {
|
||||
@Rule
|
||||
public SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
|
||||
|
||||
@Rule
|
||||
public ExtendedLongPressTimeoutRule mLongPressTimeoutRule = new ExtendedLongPressTimeoutRule();
|
||||
|
||||
public static void initialize(AbstractLauncherUiTest test) throws Exception {
|
||||
test.reinitializeLauncherData();
|
||||
test.mDevice.pressHome();
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.launcher3.util.rule;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
public class ExtendedLongPressTimeoutRule implements TestRule {
|
||||
|
||||
private static final String TAG = "ExtendedLongPressTimeoutRule";
|
||||
|
||||
private static final float LONG_PRESS_TIMEOUT_MULTIPLIER = 10f;
|
||||
|
||||
@Override
|
||||
public Statement apply(Statement base, Description description) {
|
||||
return new Statement() {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
ContentResolver contentResolver = InstrumentationRegistry.getInstrumentation()
|
||||
.getContext()
|
||||
.getContentResolver();
|
||||
int prevLongPressTimeout = Settings.Secure.getInt(
|
||||
contentResolver,
|
||||
Settings.Secure.LONG_PRESS_TIMEOUT,
|
||||
ViewConfiguration.getLongPressTimeout());
|
||||
int newLongPressTimeout =
|
||||
(int) (prevLongPressTimeout * LONG_PRESS_TIMEOUT_MULTIPLIER);
|
||||
|
||||
try {
|
||||
Log.d(TAG, "In try-block: Setting long press timeout from "
|
||||
+ prevLongPressTimeout + "ms to " + newLongPressTimeout + "ms");
|
||||
Settings.Secure.putInt(
|
||||
contentResolver,
|
||||
Settings.Secure.LONG_PRESS_TIMEOUT,
|
||||
(int) (prevLongPressTimeout * LONG_PRESS_TIMEOUT_MULTIPLIER));
|
||||
|
||||
base.evaluate();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error", e);
|
||||
throw e;
|
||||
} finally {
|
||||
Log.d(TAG, "In finally-block: resetting long press timeout to "
|
||||
+ prevLongPressTimeout + "ms");
|
||||
Settings.Secure.putInt(
|
||||
contentResolver,
|
||||
Settings.Secure.LONG_PRESS_TIMEOUT,
|
||||
prevLongPressTimeout);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1919,17 +1919,21 @@ public final class LauncherInstrumentation {
|
||||
}
|
||||
|
||||
private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
|
||||
float x, float y, int source) {
|
||||
float x, float y, int source, int toolType) {
|
||||
return MotionEvent.obtain(downTime, eventTime, action, 1,
|
||||
new MotionEvent.PointerProperties[]{getPointerProperties(0)},
|
||||
new MotionEvent.PointerProperties[]{getPointerProperties(0, toolType)},
|
||||
new MotionEvent.PointerCoords[]{getPointerCoords(x, y)},
|
||||
0, 0, 1.0f, 1.0f, 0, 0, source, 0);
|
||||
}
|
||||
|
||||
private static MotionEvent.PointerProperties getPointerProperties(int pointerId) {
|
||||
return getPointerProperties(pointerId, Configurator.getInstance().getToolType());
|
||||
}
|
||||
|
||||
private static MotionEvent.PointerProperties getPointerProperties(int pointerId, int toolType) {
|
||||
MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
|
||||
properties.id = pointerId;
|
||||
properties.toolType = Configurator.getInstance().getToolType();
|
||||
properties.toolType = toolType;
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -1975,6 +1979,19 @@ public final class LauncherInstrumentation {
|
||||
|
||||
public void sendPointer(long downTime, long currentTime, int action, Point point,
|
||||
GestureScope gestureScope, int source, boolean isRightClick) {
|
||||
sendPointer(
|
||||
downTime,
|
||||
currentTime,
|
||||
action,
|
||||
point,
|
||||
gestureScope,
|
||||
source,
|
||||
isRightClick,
|
||||
Configurator.getInstance().getToolType());
|
||||
}
|
||||
|
||||
public void sendPointer(long downTime, long currentTime, int action, Point point,
|
||||
GestureScope gestureScope, int source, boolean isRightClick, int toolType) {
|
||||
final boolean hasTIS = hasTIS();
|
||||
int pointerCount = mPointerCount;
|
||||
|
||||
@@ -2009,13 +2026,13 @@ public final class LauncherInstrumentation {
|
||||
? getTrackpadMotionEvent(
|
||||
downTime, currentTime, action, point.x, point.y, pointerCount,
|
||||
mTrackpadGestureType)
|
||||
: getMotionEvent(downTime, currentTime, action, point.x, point.y, source);
|
||||
: getMotionEvent(downTime, currentTime, action, point.x, point.y, source, toolType);
|
||||
if (action == MotionEvent.ACTION_BUTTON_PRESS
|
||||
|| action == MotionEvent.ACTION_BUTTON_RELEASE) {
|
||||
event.setActionButton(MotionEvent.BUTTON_PRIMARY);
|
||||
}
|
||||
if (isRightClick) {
|
||||
event.setButtonState(event.getButtonState() & MotionEvent.BUTTON_SECONDARY);
|
||||
event.setButtonState(event.getButtonState() | MotionEvent.BUTTON_SECONDARY);
|
||||
}
|
||||
injectEvent(event);
|
||||
}
|
||||
@@ -2114,15 +2131,19 @@ public final class LauncherInstrumentation {
|
||||
@NonNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent) {
|
||||
final Point targetCenter = target.getVisibleCenter();
|
||||
final long downTime = SystemClock.uptimeMillis();
|
||||
// Use stylus secondary button press to prevent using the exteded long press timeout rule
|
||||
// unnecessarily
|
||||
sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter,
|
||||
GestureScope.DONT_EXPECT_PILFER);
|
||||
GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
|
||||
/* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
|
||||
try {
|
||||
expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent);
|
||||
final UiObject2 result = waitForLauncherObject(resName);
|
||||
return result;
|
||||
} finally {
|
||||
sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter,
|
||||
GestureScope.DONT_EXPECT_PILFER);
|
||||
GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
|
||||
/* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user