Merge changes I3460e27e,Ib2f1d194,I28b8f266 into udc-dev
* changes: Always close the handle menu if the window loses focus Delegate window decor view binding / updating to *ViewHolders Update desktop mode caption bar to newest spec
This commit is contained in:
@@ -17,10 +17,9 @@
|
|||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24"
|
android:viewportHeight="24">
|
||||||
android:tint="@color/decor_button_dark_color">
|
|
||||||
<group android:translateY="8.0">
|
<group android:translateY="8.0">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white" android:pathData="M3,5V3H21V5Z"/>
|
android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
|
||||||
</group>
|
</group>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?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
|
||||||
|
-->
|
||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/black" android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2022 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/desktop_mode_caption"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="@drawable/desktop_mode_decor_title">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/open_menu_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:background="?android:selectableItemBackgroundBorderless">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/application_icon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:contentDescription="@string/app_icon_text" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/application_name"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:minWidth="80dp"
|
||||||
|
android:textColor="@color/desktop_mode_caption_app_name_dark"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textFontWeight="500"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingStart="4dp"
|
||||||
|
android:paddingEnd="4dp"
|
||||||
|
tools:text="Gmail"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/expand_menu_button"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:contentDescription="@string/collapse_menu_text"
|
||||||
|
android:src="@drawable/ic_baseline_expand_more_24"
|
||||||
|
android:tint="@color/desktop_mode_caption_expand_button_dark"
|
||||||
|
android:background="@null"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"
|
||||||
|
android:layout_gravity="center_vertical"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/caption_handle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/close_window"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:contentDescription="@string/close_button_text"
|
||||||
|
android:src="@drawable/decor_close_button_dark"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:gravity="end"
|
||||||
|
android:background="?android:selectableItemBackgroundBorderless"
|
||||||
|
android:tint="@color/desktop_mode_caption_close_button_dark"/>
|
||||||
|
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
android:contentDescription="@string/collapse_menu_text"
|
android:contentDescription="@string/collapse_menu_text"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:background="@drawable/caption_collapse_menu_button"
|
android:background="@drawable/ic_baseline_expand_more_24"
|
||||||
android:layout_centerVertical="true"/>
|
android:layout_centerVertical="true"/>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
@@ -16,26 +16,21 @@
|
|||||||
-->
|
-->
|
||||||
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
|
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/desktop_mode_caption"
|
android:id="@+id/desktop_mode_caption"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:background="@drawable/desktop_mode_decor_title">
|
android:background="@drawable/desktop_mode_decor_title">
|
||||||
<Button
|
|
||||||
style="@style/CaptionButtonStyle"
|
<ImageButton
|
||||||
android:id="@+id/back_button"
|
|
||||||
android:contentDescription="@string/back_button_text"
|
|
||||||
android:background="@drawable/decor_back_button_dark"/>
|
|
||||||
<Button
|
|
||||||
android:id="@+id/caption_handle"
|
android:id="@+id/caption_handle"
|
||||||
android:layout_width="128dp"
|
android:layout_width="128dp"
|
||||||
android:layout_height="32dp"
|
android:layout_height="42dp"
|
||||||
android:layout_margin="5dp"
|
|
||||||
android:contentDescription="@string/handle_text"
|
android:contentDescription="@string/handle_text"
|
||||||
android:background="@drawable/decor_handle_dark"/>
|
android:src="@drawable/decor_handle_dark"
|
||||||
<Button
|
tools:tint="@color/desktop_mode_caption_handle_bar_dark"
|
||||||
style="@style/CaptionButtonStyle"
|
android:scaleType="fitXY"
|
||||||
android:id="@+id/close_window"
|
android:background="?android:selectableItemBackground"/>
|
||||||
android:contentDescription="@string/close_button_text"
|
|
||||||
android:background="@drawable/decor_close_button_dark"/>
|
|
||||||
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
|
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
|
||||||
@@ -54,4 +54,14 @@
|
|||||||
<color name="splash_screen_bg_light">#FFFFFF</color>
|
<color name="splash_screen_bg_light">#FFFFFF</color>
|
||||||
<color name="splash_screen_bg_dark">#000000</color>
|
<color name="splash_screen_bg_dark">#000000</color>
|
||||||
<color name="splash_window_background_default">@color/splash_screen_bg_light</color>
|
<color name="splash_window_background_default">@color/splash_screen_bg_light</color>
|
||||||
|
|
||||||
|
<!-- Desktop Mode -->
|
||||||
|
<color name="desktop_mode_caption_handle_bar_light">#EFF1F2</color>
|
||||||
|
<color name="desktop_mode_caption_handle_bar_dark">#1C1C17</color>
|
||||||
|
<color name="desktop_mode_caption_expand_button_light">#EFF1F2</color>
|
||||||
|
<color name="desktop_mode_caption_expand_button_dark">#48473A</color>
|
||||||
|
<color name="desktop_mode_caption_close_button_light">#EFF1F2</color>
|
||||||
|
<color name="desktop_mode_caption_close_button_dark">#1C1C17</color>
|
||||||
|
<color name="desktop_mode_caption_app_name_light">#EFF1F2</color>
|
||||||
|
<color name="desktop_mode_caption_app_name_dark">#1C1C17</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -166,7 +166,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
decoration.relayout(taskInfo);
|
decoration.relayout(taskInfo);
|
||||||
setupCaptionColor(taskInfo, decoration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -252,7 +251,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
|||||||
}
|
}
|
||||||
} else if (id == R.id.back_button) {
|
} else if (id == R.id.back_button) {
|
||||||
mTaskOperations.injectBackKey();
|
mTaskOperations.injectBackKey();
|
||||||
} else if (id == R.id.caption_handle) {
|
} else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
|
||||||
decoration.createHandleMenu();
|
decoration.createHandleMenu();
|
||||||
} else if (id == R.id.desktop_button) {
|
} else if (id == R.id.desktop_button) {
|
||||||
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
|
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
|
||||||
@@ -262,7 +261,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
|||||||
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
|
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
|
||||||
mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
|
mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
|
||||||
decoration.closeHandleMenu();
|
decoration.closeHandleMenu();
|
||||||
decoration.setButtonVisibility(false);
|
|
||||||
} else if (id == R.id.collapse_menu_button) {
|
} else if (id == R.id.collapse_menu_button) {
|
||||||
decoration.closeHandleMenu();
|
decoration.closeHandleMenu();
|
||||||
}
|
}
|
||||||
@@ -556,13 +554,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupCaptionColor(RunningTaskInfo taskInfo,
|
|
||||||
DesktopModeWindowDecoration decoration) {
|
|
||||||
if (taskInfo == null || taskInfo.taskDescription == null) return;
|
|
||||||
final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
|
|
||||||
decoration.setCaptionColor(statusBarColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
|
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
|
||||||
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
|
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
|
||||||
return DesktopModeStatus.isProto2Enabled()
|
return DesktopModeStatus.isProto2Enabled()
|
||||||
@@ -602,7 +593,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
|||||||
windowDecoration.setDragPositioningCallback(taskPositioner);
|
windowDecoration.setDragPositioningCallback(taskPositioner);
|
||||||
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
|
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
|
||||||
windowDecoration.relayout(taskInfo, startT, finishT);
|
windowDecoration.relayout(taskInfo, startT, finishT);
|
||||||
setupCaptionColor(taskInfo, windowDecoration);
|
|
||||||
incrementEventReceiverTasks(taskInfo.displayId);
|
incrementEventReceiverTasks(taskInfo.displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,15 +22,10 @@ import android.app.ActivityManager;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.ColorStateList;
|
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.graphics.drawable.GradientDrawable;
|
|
||||||
import android.graphics.drawable.VectorDrawable;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Choreographer;
|
import android.view.Choreographer;
|
||||||
@@ -48,20 +43,24 @@ import com.android.wm.shell.common.DisplayController;
|
|||||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||||
import com.android.wm.shell.desktopmode.DesktopModeStatus;
|
import com.android.wm.shell.desktopmode.DesktopModeStatus;
|
||||||
import com.android.wm.shell.desktopmode.DesktopTasksController;
|
import com.android.wm.shell.desktopmode.DesktopTasksController;
|
||||||
|
import com.android.wm.shell.windowdecor.viewholder.DesktopModeAppControlsWindowDecorationViewHolder;
|
||||||
|
import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
|
||||||
|
import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
|
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
|
||||||
* {@link DesktopModeWindowDecorViewModel}. The caption bar contains a handle, back button, and
|
* {@link DesktopModeWindowDecorViewModel}.
|
||||||
* close button.
|
|
||||||
*
|
*
|
||||||
* The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
|
* The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
|
||||||
*/
|
*/
|
||||||
public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
|
public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
|
||||||
private static final String TAG = "DesktopModeWindowDecoration";
|
private static final String TAG = "DesktopModeWindowDecoration";
|
||||||
|
|
||||||
private final Handler mHandler;
|
private final Handler mHandler;
|
||||||
private final Choreographer mChoreographer;
|
private final Choreographer mChoreographer;
|
||||||
private final SyncTransactionQueue mSyncQueue;
|
private final SyncTransactionQueue mSyncQueue;
|
||||||
|
|
||||||
|
private DesktopModeWindowDecorationViewHolder mWindowDecorViewHolder;
|
||||||
private View.OnClickListener mOnCaptionButtonClickListener;
|
private View.OnClickListener mOnCaptionButtonClickListener;
|
||||||
private View.OnTouchListener mOnCaptionTouchListener;
|
private View.OnTouchListener mOnCaptionTouchListener;
|
||||||
private DragPositioningCallback mDragPositioningCallback;
|
private DragPositioningCallback mDragPositioningCallback;
|
||||||
@@ -73,7 +72,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
|||||||
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
|
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
|
||||||
new WindowDecoration.RelayoutResult<>();
|
new WindowDecoration.RelayoutResult<>();
|
||||||
|
|
||||||
private boolean mDesktopActive;
|
|
||||||
private AdditionalWindow mHandleMenu;
|
private AdditionalWindow mHandleMenu;
|
||||||
private final int mHandleMenuWidthId = R.dimen.freeform_decor_caption_menu_width;
|
private final int mHandleMenuWidthId = R.dimen.freeform_decor_caption_menu_width;
|
||||||
private final int mHandleMenuShadowRadiusId = R.dimen.caption_menu_shadow_radius;
|
private final int mHandleMenuShadowRadiusId = R.dimen.caption_menu_shadow_radius;
|
||||||
@@ -94,7 +92,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
|||||||
mHandler = handler;
|
mHandler = handler;
|
||||||
mChoreographer = choreographer;
|
mChoreographer = choreographer;
|
||||||
mSyncQueue = syncQueue;
|
mSyncQueue = syncQueue;
|
||||||
mDesktopActive = DesktopModeStatus.isActive(mContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -152,9 +149,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
|||||||
final int outsetRightId = R.dimen.freeform_resize_handle;
|
final int outsetRightId = R.dimen.freeform_resize_handle;
|
||||||
final int outsetBottomId = R.dimen.freeform_resize_handle;
|
final int outsetBottomId = R.dimen.freeform_resize_handle;
|
||||||
|
|
||||||
|
final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
|
||||||
|
taskInfo.getWindowingMode());
|
||||||
mRelayoutParams.reset();
|
mRelayoutParams.reset();
|
||||||
mRelayoutParams.mRunningTaskInfo = taskInfo;
|
mRelayoutParams.mRunningTaskInfo = taskInfo;
|
||||||
mRelayoutParams.mLayoutResId = R.layout.desktop_mode_window_decor;
|
mRelayoutParams.mLayoutResId = windowDecorLayoutId;
|
||||||
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
|
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
|
||||||
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
|
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
|
||||||
if (isDragResizeable) {
|
if (isDragResizeable) {
|
||||||
@@ -172,23 +171,27 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (oldRootView != mResult.mRootView) {
|
if (oldRootView != mResult.mRootView) {
|
||||||
setupRootView();
|
if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_focused_window_decor) {
|
||||||
|
mWindowDecorViewHolder = new DesktopModeFocusedWindowDecorationViewHolder(
|
||||||
|
mResult.mRootView,
|
||||||
|
mOnCaptionTouchListener,
|
||||||
|
mOnCaptionButtonClickListener
|
||||||
|
);
|
||||||
|
} else if (mRelayoutParams.mLayoutResId
|
||||||
|
== R.layout.desktop_mode_app_controls_window_decor) {
|
||||||
|
mWindowDecorViewHolder = new DesktopModeAppControlsWindowDecorationViewHolder(
|
||||||
|
mResult.mRootView,
|
||||||
|
mOnCaptionTouchListener,
|
||||||
|
mOnCaptionButtonClickListener
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unexpected layout resource id");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
mWindowDecorViewHolder.bindData(mTaskInfo);
|
||||||
|
|
||||||
// If this task is not focused, do not show caption.
|
if (!mTaskInfo.isFocused) {
|
||||||
setCaptionVisibility(mTaskInfo.isFocused);
|
closeHandleMenu();
|
||||||
|
|
||||||
if (mTaskInfo.isFocused) {
|
|
||||||
if (DesktopModeStatus.isProto2Enabled()) {
|
|
||||||
updateButtonVisibility();
|
|
||||||
} else if (DesktopModeStatus.isProto1Enabled()) {
|
|
||||||
// Only handle should show if Desktop Mode is inactive.
|
|
||||||
boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
|
|
||||||
if (mDesktopActive != desktopCurrentStatus) {
|
|
||||||
mDesktopActive = desktopCurrentStatus;
|
|
||||||
setButtonVisibility(mDesktopActive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isDragResizeable) {
|
if (!isDragResizeable) {
|
||||||
@@ -219,24 +222,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
|||||||
mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
|
mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up listeners when a new root view is created.
|
|
||||||
*/
|
|
||||||
private void setupRootView() {
|
|
||||||
final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
|
|
||||||
caption.setOnTouchListener(mOnCaptionTouchListener);
|
|
||||||
final View handle = caption.findViewById(R.id.caption_handle);
|
|
||||||
handle.setOnTouchListener(mOnCaptionTouchListener);
|
|
||||||
handle.setOnClickListener(mOnCaptionButtonClickListener);
|
|
||||||
if (DesktopModeStatus.isProto1Enabled()) {
|
|
||||||
final View back = caption.findViewById(R.id.back_button);
|
|
||||||
back.setOnClickListener(mOnCaptionButtonClickListener);
|
|
||||||
final View close = caption.findViewById(R.id.close_window);
|
|
||||||
close.setOnClickListener(mOnCaptionButtonClickListener);
|
|
||||||
}
|
|
||||||
updateButtonVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupHandleMenu() {
|
private void setupHandleMenu() {
|
||||||
final View menu = mHandleMenu.mWindowViewHost.getView();
|
final View menu = mHandleMenu.mWindowViewHost.getView();
|
||||||
final View fullscreen = menu.findViewById(R.id.fullscreen_button);
|
final View fullscreen = menu.findViewById(R.id.fullscreen_button);
|
||||||
@@ -255,98 +240,26 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
|||||||
collapse.setOnClickListener(mOnCaptionButtonClickListener);
|
collapse.setOnClickListener(mOnCaptionButtonClickListener);
|
||||||
menu.setOnTouchListener(mOnCaptionTouchListener);
|
menu.setOnTouchListener(mOnCaptionTouchListener);
|
||||||
|
|
||||||
String packageName = mTaskInfo.baseActivity.getPackageName();
|
|
||||||
PackageManager pm = mContext.getApplicationContext().getPackageManager();
|
|
||||||
// TODO(b/268363572): Use IconProvider or BaseIconCache to set drawable/name.
|
|
||||||
try {
|
|
||||||
ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
|
|
||||||
PackageManager.ApplicationInfoFlags.of(0));
|
|
||||||
final ImageView appIcon = menu.findViewById(R.id.application_icon);
|
final ImageView appIcon = menu.findViewById(R.id.application_icon);
|
||||||
appIcon.setImageDrawable(pm.getApplicationIcon(applicationInfo));
|
|
||||||
final TextView appName = menu.findViewById(R.id.application_name);
|
final TextView appName = menu.findViewById(R.id.application_name);
|
||||||
appName.setText(pm.getApplicationLabel(applicationInfo));
|
loadAppInfo(appName, appIcon);
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
Log.w(TAG, "Package not found: " + packageName, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets caption visibility based on task focus.
|
|
||||||
* Note: Only applicable to Desktop Proto 1; Proto 2 only closes handle menu on focus loss
|
|
||||||
* @param visible whether or not the caption should be visible
|
|
||||||
*/
|
|
||||||
private void setCaptionVisibility(boolean visible) {
|
|
||||||
if (!visible) closeHandleMenu();
|
|
||||||
if (!DesktopModeStatus.isProto1Enabled()) return;
|
|
||||||
final int v = visible ? View.VISIBLE : View.GONE;
|
|
||||||
final View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
|
|
||||||
captionView.setVisibility(v);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the visibility of buttons and color of caption based on desktop mode status
|
|
||||||
*/
|
|
||||||
void updateButtonVisibility() {
|
|
||||||
if (DesktopModeStatus.isProto2Enabled()) {
|
|
||||||
setButtonVisibility(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM);
|
|
||||||
} else if (DesktopModeStatus.isProto1Enabled()) {
|
|
||||||
mDesktopActive = DesktopModeStatus.isActive(mContext);
|
|
||||||
setButtonVisibility(mDesktopActive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show or hide buttons
|
|
||||||
*/
|
|
||||||
void setButtonVisibility(boolean visible) {
|
|
||||||
final int visibility = visible && DesktopModeStatus.isProto1Enabled()
|
|
||||||
? View.VISIBLE : View.GONE;
|
|
||||||
final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
|
|
||||||
final View back = caption.findViewById(R.id.back_button);
|
|
||||||
final View close = caption.findViewById(R.id.close_window);
|
|
||||||
back.setVisibility(visibility);
|
|
||||||
close.setVisibility(visibility);
|
|
||||||
final int buttonTintColorRes =
|
|
||||||
mDesktopActive ? R.color.decor_button_dark_color
|
|
||||||
: R.color.decor_button_light_color;
|
|
||||||
final ColorStateList buttonTintColor =
|
|
||||||
caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
|
|
||||||
final View handle = caption.findViewById(R.id.caption_handle);
|
|
||||||
final VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
|
|
||||||
handleBackground.setTintList(buttonTintColor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isHandleMenuActive() {
|
boolean isHandleMenuActive() {
|
||||||
return mHandleMenu != null;
|
return mHandleMenu != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCaptionColor(int captionColor) {
|
private void loadAppInfo(TextView appNameTextView, ImageView appIconImageView) {
|
||||||
if (mResult.mRootView == null) {
|
String packageName = mTaskInfo.realActivity.getPackageName();
|
||||||
return;
|
PackageManager pm = mContext.getApplicationContext().getPackageManager();
|
||||||
}
|
try {
|
||||||
|
// TODO(b/268363572): Use IconProvider or BaseIconCache to set drawable/name.
|
||||||
final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
|
ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
|
||||||
final GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
|
PackageManager.ApplicationInfoFlags.of(0));
|
||||||
captionDrawable.setColor(captionColor);
|
appNameTextView.setText(pm.getApplicationLabel(applicationInfo));
|
||||||
|
appIconImageView.setImageDrawable(pm.getApplicationIcon(applicationInfo));
|
||||||
final int buttonTintColorRes =
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
Color.valueOf(captionColor).luminance() < 0.5
|
Log.w(TAG, "Package not found: " + packageName, e);
|
||||||
? R.color.decor_button_light_color
|
|
||||||
: R.color.decor_button_dark_color;
|
|
||||||
final ColorStateList buttonTintColor =
|
|
||||||
caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
|
|
||||||
|
|
||||||
final View handle = caption.findViewById(R.id.caption_handle);
|
|
||||||
final Drawable handleBackground = handle.getBackground();
|
|
||||||
handleBackground.setTintList(buttonTintColor);
|
|
||||||
if (DesktopModeStatus.isProto1Enabled()) {
|
|
||||||
final View back = caption.findViewById(R.id.back_button);
|
|
||||||
final Drawable backBackground = back.getBackground();
|
|
||||||
backBackground.setTintList(buttonTintColor);
|
|
||||||
final View close = caption.findViewById(R.id.close_window);
|
|
||||||
final Drawable closeBackground = close.getBackground();
|
|
||||||
closeBackground.setTintList(buttonTintColor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,9 +284,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
|||||||
final int shadowRadius = loadDimensionPixelSize(resources, mHandleMenuShadowRadiusId);
|
final int shadowRadius = loadDimensionPixelSize(resources, mHandleMenuShadowRadiusId);
|
||||||
final int cornerRadius = loadDimensionPixelSize(resources, mHandleMenuCornerRadiusId);
|
final int cornerRadius = loadDimensionPixelSize(resources, mHandleMenuCornerRadiusId);
|
||||||
|
|
||||||
final int x = mRelayoutParams.mCaptionX + (captionWidth / 2) - (menuWidth / 2)
|
final int x, y;
|
||||||
|
if (mRelayoutParams.mLayoutResId
|
||||||
|
== R.layout.desktop_mode_app_controls_window_decor) {
|
||||||
|
// Align the handle menu to the left of the caption.
|
||||||
|
x = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX;
|
||||||
|
y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
|
||||||
|
} else {
|
||||||
|
// Position the handle menu at the center of the caption.
|
||||||
|
x = mRelayoutParams.mCaptionX + (captionWidth / 2) - (menuWidth / 2)
|
||||||
- mResult.mDecorContainerOffsetX;
|
- mResult.mDecorContainerOffsetX;
|
||||||
final int y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
|
y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
|
||||||
|
}
|
||||||
mHandleMenuPosition.set(x, y);
|
mHandleMenuPosition.set(x, y);
|
||||||
String namePrefix = "Caption Menu";
|
String namePrefix = "Caption Menu";
|
||||||
mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, x, y,
|
mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, x, y,
|
||||||
@@ -503,6 +425,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
|||||||
super.close();
|
super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getDesktopModeWindowDecorLayoutId(int windowingMode) {
|
||||||
|
if (DesktopModeStatus.isProto1Enabled()) {
|
||||||
|
return R.layout.desktop_mode_app_controls_window_decor;
|
||||||
|
}
|
||||||
|
return windowingMode == WINDOWING_MODE_FREEFORM
|
||||||
|
? R.layout.desktop_mode_app_controls_window_decor
|
||||||
|
: R.layout.desktop_mode_focused_window_decor;
|
||||||
|
}
|
||||||
|
|
||||||
static class Factory {
|
static class Factory {
|
||||||
|
|
||||||
DesktopModeWindowDecoration create(
|
DesktopModeWindowDecoration create(
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
|
|||||||
};
|
};
|
||||||
|
|
||||||
RunningTaskInfo mTaskInfo;
|
RunningTaskInfo mTaskInfo;
|
||||||
|
int mLayoutResId;
|
||||||
final SurfaceControl mTaskSurface;
|
final SurfaceControl mTaskSurface;
|
||||||
|
|
||||||
Display mDisplay;
|
Display mDisplay;
|
||||||
@@ -162,6 +163,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
|
|||||||
if (params.mRunningTaskInfo != null) {
|
if (params.mRunningTaskInfo != null) {
|
||||||
mTaskInfo = params.mRunningTaskInfo;
|
mTaskInfo = params.mRunningTaskInfo;
|
||||||
}
|
}
|
||||||
|
final int oldLayoutResId = mLayoutResId;
|
||||||
|
mLayoutResId = params.mLayoutResId;
|
||||||
|
|
||||||
if (!mTaskInfo.isVisible) {
|
if (!mTaskInfo.isVisible) {
|
||||||
releaseViews();
|
releaseViews();
|
||||||
@@ -178,7 +181,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
|
|||||||
final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo);
|
final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo);
|
||||||
if (oldTaskConfig.densityDpi != taskConfig.densityDpi
|
if (oldTaskConfig.densityDpi != taskConfig.densityDpi
|
||||||
|| mDisplay == null
|
|| mDisplay == null
|
||||||
|| mDisplay.getDisplayId() != mTaskInfo.displayId) {
|
|| mDisplay.getDisplayId() != mTaskInfo.displayId
|
||||||
|
|| oldLayoutResId != mLayoutResId) {
|
||||||
releaseViews();
|
releaseViews();
|
||||||
|
|
||||||
if (!obtainDisplayOrRegisterListener()) {
|
if (!obtainDisplayOrRegisterListener()) {
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package com.android.wm.shell.windowdecor.viewholder
|
||||||
|
|
||||||
|
import android.app.ActivityManager.RunningTaskInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageButton
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.android.wm.shell.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A desktop mode window decoration used when the window is floating (i.e. freeform). It hosts
|
||||||
|
* finer controls such as a close window button and an "app info" section to pull up additional
|
||||||
|
* controls.
|
||||||
|
*/
|
||||||
|
internal class DesktopModeAppControlsWindowDecorationViewHolder(
|
||||||
|
rootView: View,
|
||||||
|
onCaptionTouchListener: View.OnTouchListener,
|
||||||
|
onCaptionButtonClickListener: View.OnClickListener
|
||||||
|
) : DesktopModeWindowDecorationViewHolder(rootView) {
|
||||||
|
|
||||||
|
private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
|
||||||
|
private val captionHandle: View = rootView.findViewById(R.id.caption_handle)
|
||||||
|
private val openMenuButton: View = rootView.findViewById(R.id.open_menu_button)
|
||||||
|
private val closeWindowButton: ImageButton = rootView.findViewById(R.id.close_window)
|
||||||
|
private val expandMenuButton: ImageButton = rootView.findViewById(R.id.expand_menu_button)
|
||||||
|
private val appNameTextView: TextView = rootView.findViewById(R.id.application_name)
|
||||||
|
private val appIconImageView: ImageView = rootView.findViewById(R.id.application_icon)
|
||||||
|
|
||||||
|
init {
|
||||||
|
captionView.setOnTouchListener(onCaptionTouchListener)
|
||||||
|
captionHandle.setOnTouchListener(onCaptionTouchListener)
|
||||||
|
openMenuButton.setOnClickListener(onCaptionButtonClickListener)
|
||||||
|
closeWindowButton.setOnClickListener(onCaptionButtonClickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindData(taskInfo: RunningTaskInfo) {
|
||||||
|
bindAppInfo(taskInfo)
|
||||||
|
|
||||||
|
val captionDrawable = captionView.background as GradientDrawable
|
||||||
|
captionDrawable.setColor(taskInfo.taskDescription.statusBarColor)
|
||||||
|
|
||||||
|
closeWindowButton.imageTintList = ColorStateList.valueOf(
|
||||||
|
getCaptionCloseButtonColor(taskInfo))
|
||||||
|
expandMenuButton.imageTintList = ColorStateList.valueOf(
|
||||||
|
getCaptionExpandButtonColor(taskInfo))
|
||||||
|
appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindAppInfo(taskInfo: RunningTaskInfo) {
|
||||||
|
val packageName: String = taskInfo.realActivity.packageName
|
||||||
|
val pm: PackageManager = context.applicationContext.packageManager
|
||||||
|
try {
|
||||||
|
// TODO(b/268363572): Use IconProvider or BaseIconCache to set drawable/name.
|
||||||
|
val applicationInfo = pm.getApplicationInfo(packageName,
|
||||||
|
PackageManager.ApplicationInfoFlags.of(0))
|
||||||
|
appNameTextView.text = pm.getApplicationLabel(applicationInfo)
|
||||||
|
appIconImageView.setImageDrawable(pm.getApplicationIcon(applicationInfo))
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
Log.w(TAG, "Package not found: $packageName", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCaptionAppNameTextColor(taskInfo: RunningTaskInfo): Int {
|
||||||
|
return if (shouldUseLightCaptionColors(taskInfo)) {
|
||||||
|
context.getColor(R.color.desktop_mode_caption_app_name_light)
|
||||||
|
} else {
|
||||||
|
context.getColor(R.color.desktop_mode_caption_app_name_dark)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCaptionCloseButtonColor(taskInfo: RunningTaskInfo): Int {
|
||||||
|
return if (shouldUseLightCaptionColors(taskInfo)) {
|
||||||
|
context.getColor(R.color.desktop_mode_caption_close_button_light)
|
||||||
|
} else {
|
||||||
|
context.getColor(R.color.desktop_mode_caption_close_button_dark)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCaptionExpandButtonColor(taskInfo: RunningTaskInfo): Int {
|
||||||
|
return if (shouldUseLightCaptionColors(taskInfo)) {
|
||||||
|
context.getColor(R.color.desktop_mode_caption_expand_button_light)
|
||||||
|
} else {
|
||||||
|
context.getColor(R.color.desktop_mode_caption_expand_button_dark)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "DesktopModeAppControlsWindowDecorationViewHolder"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.android.wm.shell.windowdecor.viewholder
|
||||||
|
|
||||||
|
import android.app.ActivityManager.RunningTaskInfo
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageButton
|
||||||
|
import com.android.wm.shell.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen). It
|
||||||
|
* hosts a simple handle bar from which to initiate a drag motion to enter desktop mode.
|
||||||
|
*/
|
||||||
|
internal class DesktopModeFocusedWindowDecorationViewHolder(
|
||||||
|
rootView: View,
|
||||||
|
onCaptionTouchListener: View.OnTouchListener,
|
||||||
|
onCaptionButtonClickListener: View.OnClickListener
|
||||||
|
) : DesktopModeWindowDecorationViewHolder(rootView) {
|
||||||
|
|
||||||
|
private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
|
||||||
|
private val captionHandle: ImageButton = rootView.findViewById(R.id.caption_handle)
|
||||||
|
|
||||||
|
init {
|
||||||
|
captionView.setOnTouchListener(onCaptionTouchListener)
|
||||||
|
captionHandle.setOnTouchListener(onCaptionTouchListener)
|
||||||
|
captionHandle.setOnClickListener(onCaptionButtonClickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindData(taskInfo: RunningTaskInfo) {
|
||||||
|
val captionColor = taskInfo.taskDescription.statusBarColor
|
||||||
|
val captionDrawable = captionView.background as GradientDrawable
|
||||||
|
captionDrawable.setColor(captionColor)
|
||||||
|
|
||||||
|
captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
|
||||||
|
return if (shouldUseLightCaptionColors(taskInfo)) {
|
||||||
|
context.getColor(R.color.desktop_mode_caption_handle_bar_light)
|
||||||
|
} else {
|
||||||
|
context.getColor(R.color.desktop_mode_caption_handle_bar_dark)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.android.wm.shell.windowdecor.viewholder
|
||||||
|
|
||||||
|
import android.app.ActivityManager.RunningTaskInfo
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.view.View
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates the root [View] of a window decoration and its children to facilitate looking up
|
||||||
|
* children (via findViewById) and updating to the latest data from [RunningTaskInfo].
|
||||||
|
*/
|
||||||
|
internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) {
|
||||||
|
val context: Context = rootView.context
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A signal to the view holder that new data is available and that the views should be updated
|
||||||
|
* to reflect it.
|
||||||
|
*/
|
||||||
|
abstract fun bindData(taskInfo: RunningTaskInfo)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the caption items should use the 'light' color variant so that there's good contrast
|
||||||
|
* with the caption background color.
|
||||||
|
*/
|
||||||
|
protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
|
||||||
|
return Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user