Snap for 11916113 from 9715761427 to 24Q4-release
Change-Id: I6d281d12b775f9a3b30de30338164856e2949708
This commit is contained in:
@@ -27,6 +27,8 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
|
||||
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
|
||||
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_NONE;
|
||||
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
|
||||
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
|
||||
import static com.android.wm.shell.common.split.SplitScreenConstants.isPersistentSnapPosition;
|
||||
@@ -186,6 +188,10 @@ public class AppPairsController {
|
||||
}
|
||||
|
||||
@PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
|
||||
if (snapPosition == SNAP_TO_NONE) {
|
||||
// Free snap mode is enabled, just save it as 50/50 split.
|
||||
snapPosition = SNAP_TO_50_50;
|
||||
}
|
||||
if (!isPersistentSnapPosition(snapPosition)) {
|
||||
// If we received an illegal snap position, log an error and do not create the app pair
|
||||
Log.wtf(TAG, "Tried to save an app pair with illegal snapPosition "
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
@@ -49,6 +50,7 @@ import java.util.Arrays;
|
||||
*/
|
||||
public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayout
|
||||
implements OnClickListener, Insettable {
|
||||
public static final String TAG = "OverviewActionsView";
|
||||
private final Rect mInsets = new Rect();
|
||||
|
||||
@IntDef(flag = true, value = {
|
||||
@@ -254,6 +256,8 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
|
||||
* pair.
|
||||
*/
|
||||
public void updateForGroupedTask(boolean isGroupedTask, boolean canSaveAppPair) {
|
||||
Log.d(TAG, "updateForGroupedTask() called with: isGroupedTask = [" + isGroupedTask
|
||||
+ "], canSaveAppPair = [" + canSaveAppPair + "]");
|
||||
mIsGroupedTask = isGroupedTask;
|
||||
mCanSaveAppPair = canSaveAppPair;
|
||||
updateActionButtonsVisibility();
|
||||
@@ -273,6 +277,8 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
|
||||
assert mDp != null;
|
||||
boolean showSingleTaskActions = !mIsGroupedTask;
|
||||
boolean showGroupActions = mIsGroupedTask && mDp.isTablet && mCanSaveAppPair;
|
||||
Log.d(TAG, "updateActionButtonsVisibility() called: showSingleTaskActions = ["
|
||||
+ showSingleTaskActions + ", showGroupActions = [" + showGroupActions + "]");
|
||||
getActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showSingleTaskActions ? 1 : 0);
|
||||
getGroupActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showGroupActions ? 1 : 0);
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> ex
|
||||
case VIEW_TYPE_PRIVATE_SPACE_HEADER:
|
||||
RelativeLayout psHeaderLayout = holder.itemView.findViewById(
|
||||
R.id.ps_header_layout);
|
||||
mApps.getPrivateProfileManager().addPrivateSpaceHeaderViewElements(psHeaderLayout);
|
||||
mApps.getPrivateProfileManager().bindPrivateSpaceHeaderViewElements(psHeaderLayout);
|
||||
AdapterItem adapterItem = mApps.getAdapterItems().get(position);
|
||||
int roundRegions = ROUND_TOP_LEFT | ROUND_TOP_RIGHT;
|
||||
if (mApps.getPrivateProfileManager().getCurrentState() == STATE_DISABLED) {
|
||||
|
||||
@@ -104,6 +104,7 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
private static final int LOCK_TEXT_OPACITY_DELAY = 500;
|
||||
private static final int MASK_VIEW_DELAY = 400;
|
||||
private static final int NO_DELAY = 0;
|
||||
private static final int CONTAINER_OPACITY_DURATION = 150;
|
||||
private final ActivityAllAppsContainerView<?> mAllApps;
|
||||
private final Predicate<UserHandle> mPrivateProfileMatcher;
|
||||
private final int mPsHeaderHeight;
|
||||
@@ -196,23 +197,6 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
mAllApps.mAH.get(MAIN).mAdapter.notifyItemInserted(adapterItems.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables quiet mode for Private Space User Profile.
|
||||
* When called from search, a runnable is set and executed in the {@link #reset()} method, when
|
||||
* Launcher receives update about profile availability.
|
||||
* The runnable is only executed once, and reset after execution.
|
||||
* In case the method is called again, before the previously set runnable was executed,
|
||||
* the runnable will be updated.
|
||||
*/
|
||||
public void unlockPrivateProfile() {
|
||||
setQuietMode(false);
|
||||
}
|
||||
|
||||
/** Enables quiet mode for Private Space User Profile. */
|
||||
void lockPrivateProfile() {
|
||||
setQuietMode(true);
|
||||
}
|
||||
|
||||
/** Whether private profile should be hidden on Launcher. */
|
||||
public boolean isPrivateSpaceHidden() {
|
||||
return getCurrentState() == STATE_DISABLED && SettingsCache.INSTANCE
|
||||
@@ -377,7 +361,7 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
}
|
||||
|
||||
/** Add Private Space Header view elements based upon {@link UserProfileState} */
|
||||
public void addPrivateSpaceHeaderViewElements(RelativeLayout parent) {
|
||||
public void bindPrivateSpaceHeaderViewElements(RelativeLayout parent) {
|
||||
mPSHeader = parent;
|
||||
if (mOnPSHeaderAdded != null) {
|
||||
MAIN_EXECUTOR.execute(mOnPSHeaderAdded);
|
||||
@@ -395,27 +379,27 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
//Add quietMode image and action for lock/unlock button
|
||||
ViewGroup lockButton = mPSHeader.findViewById(R.id.ps_lock_unlock_button);
|
||||
assert lockButton != null;
|
||||
addLockButton(lockButton);
|
||||
updateLockButton(lockButton);
|
||||
|
||||
//Trigger lock/unlock action from header.
|
||||
addHeaderOnClickListener(mPSHeader);
|
||||
updateHeaderOnClickListener(mPSHeader);
|
||||
|
||||
//Add image and action for private space settings button
|
||||
ImageButton settingsButton = mPSHeader.findViewById(R.id.ps_settings_button);
|
||||
assert settingsButton != null;
|
||||
addPrivateSpaceSettingsButton(settingsButton);
|
||||
updatePrivateSpaceSettingsButton(settingsButton);
|
||||
|
||||
//Add image for private space transitioning view
|
||||
ImageView transitionView = parent.findViewById(R.id.ps_transition_image);
|
||||
assert transitionView != null;
|
||||
addTransitionImage(transitionView);
|
||||
updateTransitionImage(transitionView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the quietModeButton and attach onClickListener for the header to animate different
|
||||
* states when clicked.
|
||||
*/
|
||||
private void addLockButton(ViewGroup lockButton) {
|
||||
private void updateLockButton(ViewGroup lockButton) {
|
||||
TextView lockText = lockButton.findViewById(R.id.lock_text);
|
||||
switch (getCurrentState()) {
|
||||
case STATE_ENABLED -> {
|
||||
@@ -434,7 +418,7 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void addHeaderOnClickListener(RelativeLayout header) {
|
||||
private void updateHeaderOnClickListener(RelativeLayout header) {
|
||||
if (getCurrentState() == STATE_DISABLED) {
|
||||
header.setOnClickListener(view -> lockingAction(/* lock */ false));
|
||||
header.setClickable(true);
|
||||
@@ -452,14 +436,10 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
/** Sets the enablement of the profile when header or button is clicked. */
|
||||
private void lockingAction(boolean lock) {
|
||||
logEvents(lock ? LAUNCHER_PRIVATE_SPACE_LOCK_TAP : LAUNCHER_PRIVATE_SPACE_UNLOCK_TAP);
|
||||
if (lock) {
|
||||
lockPrivateProfile();
|
||||
} else {
|
||||
unlockPrivateProfile();
|
||||
}
|
||||
setQuietMode(lock);
|
||||
}
|
||||
|
||||
private void addPrivateSpaceSettingsButton(ImageButton settingsButton) {
|
||||
private void updatePrivateSpaceSettingsButton(ImageButton settingsButton) {
|
||||
if (getCurrentState() == STATE_ENABLED
|
||||
&& isPrivateSpaceSettingsAvailable()) {
|
||||
settingsButton.setVisibility(VISIBLE);
|
||||
@@ -473,7 +453,7 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void addTransitionImage(ImageView transitionImage) {
|
||||
private void updateTransitionImage(ImageView transitionImage) {
|
||||
if (getCurrentState() == STATE_TRANSITION) {
|
||||
transitionImage.setVisibility(VISIBLE);
|
||||
} else {
|
||||
@@ -497,7 +477,10 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
return LinearSmoothScroller.SNAP_TO_END;
|
||||
}
|
||||
};
|
||||
smoothScroller.setTargetPosition(i);
|
||||
// If privateSpaceHidden() then the entire container decorator will be invisible and
|
||||
// we can directly move to an element above the header. There should always be one
|
||||
// element, as PS is present in the bottom of All Apps.
|
||||
smoothScroller.setTargetPosition(isPrivateSpaceHidden() ? i - 1 : i);
|
||||
RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager();
|
||||
if (layoutManager != null) {
|
||||
startAnimationScroll(allAppsRecyclerView, layoutManager, smoothScroller);
|
||||
@@ -619,8 +602,11 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
float newAlpha = (float) valueAnimator.getAnimatedValue();
|
||||
for (int i = 0; i < allAppsAdapterItems.size(); i++) {
|
||||
BaseAllAppsAdapter.AdapterItem currentItem = allAppsAdapterItems.get(i);
|
||||
// When not hidden: Fade all PS items except header.
|
||||
// When hidden: Fade all items.
|
||||
if (isPrivateSpaceItem(currentItem) &&
|
||||
currentItem.viewType != VIEW_TYPE_PRIVATE_SPACE_HEADER) {
|
||||
(currentItem.viewType != VIEW_TYPE_PRIVATE_SPACE_HEADER
|
||||
|| isPrivateSpaceHidden())) {
|
||||
RecyclerView.ViewHolder viewHolder =
|
||||
allAppsRecyclerView.findViewHolderForAdapterPosition(i);
|
||||
if (viewHolder != null) {
|
||||
@@ -702,10 +688,9 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
translateFloatingMaskView(false));
|
||||
} else {
|
||||
if (isPrivateSpaceHidden()) {
|
||||
animatorSet.playSequentially(translateFloatingMaskView(false),
|
||||
animateAlphaOfIcons(false),
|
||||
animateCollapseAnimation(),
|
||||
fadeOutHeaderAlpha());
|
||||
animatorSet.playSequentially(animateAlphaOfIcons(false),
|
||||
animateAlphaOfPrivateSpaceContainer(),
|
||||
animateCollapseAnimation());
|
||||
} else {
|
||||
animatorSet.playSequentially(translateFloatingMaskView(true),
|
||||
animateAlphaOfIcons(false),
|
||||
@@ -715,22 +700,23 @@ public class PrivateProfileManager extends UserProfileManager {
|
||||
animatorSet.start();
|
||||
}
|
||||
|
||||
/** Fades out the private space container. */
|
||||
private ValueAnimator fadeOutHeaderAlpha() {
|
||||
if (mPSHeader == null) {
|
||||
return new ValueAnimator();
|
||||
}
|
||||
float from = 1;
|
||||
float to = 0;
|
||||
ValueAnimator alphaAnim = ObjectAnimator.ofFloat(from, to);
|
||||
alphaAnim.setDuration(EXPAND_COLLAPSE_DURATION);
|
||||
alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
if (mPSHeader != null) {
|
||||
mPSHeader.setAlpha((float) valueAnimator.getAnimatedValue());
|
||||
/** Fades out the private space container (defined by its items' decorators). */
|
||||
private ValueAnimator animateAlphaOfPrivateSpaceContainer() {
|
||||
int from = 255; // 100% opacity.
|
||||
int to = 0; // No opacity.
|
||||
ValueAnimator alphaAnim = ObjectAnimator.ofInt(from, to);
|
||||
AllAppsRecyclerView allAppsRecyclerView = mAllApps.getActiveRecyclerView();
|
||||
List<BaseAllAppsAdapter.AdapterItem> allAppsAdapterItems =
|
||||
allAppsRecyclerView.getApps().getAdapterItems();
|
||||
alphaAnim.setDuration(CONTAINER_OPACITY_DURATION);
|
||||
alphaAnim.addUpdateListener(valueAnimator -> {
|
||||
for (BaseAllAppsAdapter.AdapterItem currentItem : allAppsAdapterItems) {
|
||||
if (isPrivateSpaceItem(currentItem)) {
|
||||
currentItem.setDecorationFillAlpha((int) valueAnimator.getAnimatedValue());
|
||||
}
|
||||
}
|
||||
// Invalidate the parent view, to redraw the decorations with changed alpha.
|
||||
allAppsRecyclerView.invalidate();
|
||||
});
|
||||
return alphaAnim;
|
||||
}
|
||||
|
||||
+14
-12
@@ -32,7 +32,7 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
|
||||
@SmallTest
|
||||
@@ -51,20 +51,21 @@ class WidgetDragScaleUtilsTest {
|
||||
deviceProfile =
|
||||
Mockito.spy(LauncherAppState.getIDP(context).getDeviceProfile(context).copy(context))
|
||||
|
||||
doAnswer {
|
||||
return@doAnswer 0.8f
|
||||
}
|
||||
.whenever(deviceProfile)
|
||||
.getWorkspaceSpringLoadScale(any(Context::class.java))
|
||||
whenever(deviceProfile.cellSize).thenReturn(Point(CELL_SIZE, CELL_SIZE))
|
||||
doReturn(0.8f)
|
||||
.whenever(deviceProfile).getWorkspaceSpringLoadScale(any(Context::class.java))
|
||||
deviceProfile.cellLayoutBorderSpacePx = Point(CELL_SPACING, CELL_SPACING)
|
||||
deviceProfile.widgetPadding.setEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getWidgetDragScalePx_largeDraggedView_downScaled() {
|
||||
val minSize = context.resources.getDimensionPixelSize(
|
||||
R.dimen.widget_drag_view_min_scale_down_size)
|
||||
whenever(deviceProfile.cellSize).thenReturn(Point(minSize * 2, minSize * 2))
|
||||
|
||||
itemInfo.spanX = 2
|
||||
itemInfo.spanY = 2
|
||||
|
||||
val widgetSize = WidgetSizes.getWidgetSizePx(deviceProfile, itemInfo.spanX, itemInfo.spanY)
|
||||
// Assume dragged view was a drawable which was larger than widget's size.
|
||||
val draggedViewWidthPx = widgetSize.width + 0.5f * widgetSize.width
|
||||
@@ -84,6 +85,9 @@ class WidgetDragScaleUtilsTest {
|
||||
|
||||
@Test
|
||||
fun getWidgetDragScalePx_draggedViewSameAsWidgetSize_downScaled() {
|
||||
val minSize = context.resources.getDimensionPixelSize(
|
||||
R.dimen.widget_drag_view_min_scale_down_size)
|
||||
whenever(deviceProfile.cellSize).thenReturn(Point(minSize * 2, minSize * 2))
|
||||
itemInfo.spanX = 4
|
||||
itemInfo.spanY = 2
|
||||
|
||||
@@ -109,14 +113,13 @@ class WidgetDragScaleUtilsTest {
|
||||
|
||||
@Test
|
||||
fun getWidgetDragScalePx_draggedViewSmallerThanMinSize_scaledSizeIsAtLeastMinSize() {
|
||||
itemInfo.spanX = 1
|
||||
itemInfo.spanY = 1
|
||||
val minSizePx =
|
||||
context.resources.getDimensionPixelSize(R.dimen.widget_drag_view_min_scale_down_size)
|
||||
|
||||
// Assume min size is greater than cell size, so that, we know the upscale of dragged view
|
||||
// is due to min size enforcement.
|
||||
assumeTrue(minSizePx > CELL_SIZE)
|
||||
whenever(deviceProfile.cellSize).thenReturn(Point(minSizePx / 2, minSizePx / 2))
|
||||
itemInfo.spanX = 1
|
||||
itemInfo.spanY = 1
|
||||
|
||||
val draggedViewWidthPx = minSizePx - 15f
|
||||
val draggedViewHeightPx = minSizePx - 15f
|
||||
@@ -142,7 +145,6 @@ class WidgetDragScaleUtilsTest {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CELL_SIZE = 60
|
||||
const val CELL_SPACING = 10
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ public class PrivateProfileManagerTest {
|
||||
public void lockPrivateProfile_requestsQuietModeAsTrue() throws Exception {
|
||||
when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)).thenReturn(false);
|
||||
|
||||
mPrivateProfileManager.lockPrivateProfile();
|
||||
mPrivateProfileManager.setQuietMode(true /* lock */);
|
||||
|
||||
awaitTasksCompleted();
|
||||
Mockito.verify(mUserManager).requestQuietModeEnabled(true, PRIVATE_HANDLE);
|
||||
@@ -140,7 +140,7 @@ public class PrivateProfileManagerTest {
|
||||
public void unlockPrivateProfile_requestsQuietModeAsFalse() throws Exception {
|
||||
when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)).thenReturn(true);
|
||||
|
||||
mPrivateProfileManager.unlockPrivateProfile();
|
||||
mPrivateProfileManager.setQuietMode(false /* unlock */);
|
||||
|
||||
awaitTasksCompleted();
|
||||
Mockito.verify(mUserManager).requestQuietModeEnabled(false, PRIVATE_HANDLE);
|
||||
@@ -176,7 +176,7 @@ public class PrivateProfileManagerTest {
|
||||
doNothing().when(privateProfileManager).expandPrivateSpace();
|
||||
when(privateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED);
|
||||
|
||||
privateProfileManager.unlockPrivateProfile();
|
||||
privateProfileManager.setQuietMode(false /* unlock */);
|
||||
privateProfileManager.reset();
|
||||
|
||||
awaitTasksCompleted();
|
||||
@@ -194,7 +194,7 @@ public class PrivateProfileManagerTest {
|
||||
doNothing().when(privateProfileManager).expandPrivateSpace();
|
||||
when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
|
||||
|
||||
privateProfileManager.lockPrivateProfile();
|
||||
privateProfileManager.setQuietMode(true /* lock */);
|
||||
privateProfileManager.reset();
|
||||
|
||||
awaitTasksCompleted();
|
||||
|
||||
@@ -133,7 +133,7 @@ public class PrivateSpaceHeaderViewTest {
|
||||
Bitmap unlockButton = getBitmap(mContext.getDrawable(R.drawable.ic_lock));
|
||||
PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
|
||||
when(privateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED);
|
||||
privateProfileManager.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
|
||||
privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout);
|
||||
awaitTasksCompleted();
|
||||
|
||||
int totalContainerHeaderView = 0;
|
||||
@@ -168,7 +168,7 @@ public class PrivateSpaceHeaderViewTest {
|
||||
PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
|
||||
when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
|
||||
when(privateProfileManager.isPrivateSpaceSettingsAvailable()).thenReturn(true);
|
||||
privateProfileManager.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
|
||||
privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout);
|
||||
awaitTasksCompleted();
|
||||
|
||||
int totalContainerHeaderView = 0;
|
||||
@@ -210,7 +210,7 @@ public class PrivateSpaceHeaderViewTest {
|
||||
PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
|
||||
when(privateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
|
||||
when(privateProfileManager.isPrivateSpaceSettingsAvailable()).thenReturn(false);
|
||||
privateProfileManager.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
|
||||
privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout);
|
||||
awaitTasksCompleted();
|
||||
|
||||
int totalContainerHeaderView = 0;
|
||||
@@ -248,7 +248,7 @@ public class PrivateSpaceHeaderViewTest {
|
||||
Bitmap transitionImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_transition_image));
|
||||
PrivateProfileManager privateProfileManager = spy(mPrivateProfileManager);
|
||||
when(privateProfileManager.getCurrentState()).thenReturn(STATE_TRANSITION);
|
||||
privateProfileManager.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
|
||||
privateProfileManager.bindPrivateSpaceHeaderViewElements(mPsHeaderLayout);
|
||||
awaitTasksCompleted();
|
||||
|
||||
int totalContainerHeaderView = 0;
|
||||
|
||||
Reference in New Issue
Block a user