Merge "Optimizing some layouts in taskview" into ub-launcher3-qt-dev

am: 552b3fe811

Change-Id: Iddee5000935e8070bce412bb0cce889a39c79617
This commit is contained in:
Sunny Goyal
2019-06-13 12:18:47 -07:00
committed by android-build-merger
10 changed files with 272 additions and 145 deletions

View File

@@ -1,7 +0,0 @@
<com.android.quickstep.hints.ProactiveHintsContainer
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:gravity="center_horizontal">
</com.android.quickstep.hints.ProactiveHintsContainer>

View File

@@ -18,9 +18,11 @@ package com.android.quickstep.views;
import static android.provider.Settings.ACTION_APP_USAGE_SETTINGS;
import static com.android.launcher3.Utilities.prefixTextWithIcon;
import android.annotation.TargetApi;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.AppUsageLimit;
@@ -28,16 +30,16 @@ import android.icu.text.MeasureFormat;
import android.icu.text.MeasureFormat.FormatWidth;
import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
import android.os.Build;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.StringRes;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -46,45 +48,72 @@ import com.android.systemui.shared.recents.model.Task;
import java.time.Duration;
import java.util.Locale;
public final class DigitalWellBeingToast extends LinearLayout {
@TargetApi(Build.VERSION_CODES.Q)
public final class DigitalWellBeingToast {
static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS);
static final int MINUTE_MS = 60000;
private final LauncherApps mLauncherApps;
public interface InitializeCallback {
void call(String contentDescription);
}
private static final String TAG = DigitalWellBeingToast.class.getSimpleName();
private final BaseDraggingActivity mActivity;
private final TaskView mTaskView;
private final LauncherApps mLauncherApps;
private Task mTask;
private TextView mText;
private boolean mHasLimit;
private long mAppRemainingTimeMs;
public DigitalWellBeingToast(Context context, AttributeSet attrs) {
super(context, attrs);
setLayoutDirection(Utilities.isRtl(getResources()) ?
View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
setOnClickListener((view) -> openAppUsageSettings());
mLauncherApps = context.getSystemService(LauncherApps.class);
public DigitalWellBeingToast(BaseDraggingActivity activity, TaskView taskView) {
mActivity = activity;
mTaskView = taskView;
mLauncherApps = activity.getSystemService(LauncherApps.class);
}
public TextView getTextView() {
return mText;
private void setTaskFooter(View view) {
View oldFooter = mTaskView.setFooter(TaskView.INDEX_DIGITAL_WELLBEING_TOAST, view);
if (oldFooter != null) {
oldFooter.setOnClickListener(null);
mActivity.getViewCache().recycleView(R.layout.digital_wellbeing_toast, oldFooter);
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mText = findViewById(R.id.digital_well_being_remaining_time);
private void setNoLimit() {
mHasLimit = false;
mTaskView.setContentDescription(mTask.titleDescription);
setTaskFooter(null);
mAppRemainingTimeMs = 0;
}
public void initialize(Task task, InitializeCallback callback) {
private void setLimit(long appUsageLimitTimeMs, long appRemainingTimeMs) {
mAppRemainingTimeMs = appRemainingTimeMs;
mHasLimit = true;
TextView toast = mActivity.getViewCache().getView(R.layout.digital_wellbeing_toast,
mActivity, mTaskView);
toast.setText(prefixTextWithIcon(mActivity, R.drawable.ic_hourglass_top, getText()));
toast.setOnClickListener(this::openAppUsageSettings);
setTaskFooter(toast);
mTaskView.setContentDescription(
getContentDescriptionForTask(mTask, appUsageLimitTimeMs, appRemainingTimeMs));
RecentsView rv = mTaskView.getRecentsView();
if (rv != null) {
rv.onDigitalWellbeingToastShown();
}
}
public String getText() {
return getText(mAppRemainingTimeMs);
}
public boolean hasLimit() {
return mHasLimit;
}
public void initialize(Task task) {
mTask = task;
if (task.key.userId != UserHandle.myUserId()) {
setVisibility(GONE);
callback.call(task.titleDescription);
setNoLimit();
return;
}
@@ -98,16 +127,12 @@ public final class DigitalWellBeingToast extends LinearLayout {
final long appRemainingTimeMs =
usageLimit != null ? usageLimit.getUsageRemaining() : -1;
post(() -> {
mTaskView.post(() -> {
if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) {
setVisibility(GONE);
setNoLimit();
} else {
setVisibility(VISIBLE);
mText.setText(getText(appRemainingTimeMs));
setLimit(appUsageLimitTimeMs, appRemainingTimeMs);
}
callback.call(getContentDescriptionForTask(
task, appUsageLimitTimeMs, appRemainingTimeMs));
});
});
}
@@ -146,7 +171,7 @@ public final class DigitalWellBeingToast extends LinearLayout {
// Use a specific string for usage less than one minute but non-zero.
if (duration.compareTo(Duration.ZERO) > 0) {
return getResources().getString(durationLessThanOneMinuteStringId);
return mActivity.getString(durationLessThanOneMinuteStringId);
}
// Otherwise, return 0-minute string.
@@ -176,24 +201,24 @@ public final class DigitalWellBeingToast extends LinearLayout {
}
private String getText(long remainingTime) {
return getResources().getString(
return mActivity.getString(
R.string.time_left_for_app,
getRoundedUpToMinuteReadableDuration(remainingTime));
}
public void openAppUsageSettings() {
public void openAppUsageSettings(View view) {
final Intent intent = new Intent(OPEN_APP_USAGE_SETTINGS_TEMPLATE)
.putExtra(Intent.EXTRA_PACKAGE_NAME,
mTask.getTopComponent().getPackageName()).addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
try {
final BaseActivity activity = BaseActivity.fromContext(getContext());
final BaseActivity activity = BaseActivity.fromContext(view.getContext());
final ActivityOptions options = ActivityOptions.makeScaleUpAnimation(
this, 0, 0,
getWidth(), getHeight());
view, 0, 0,
view.getWidth(), view.getHeight());
activity.startActivity(intent, options.toBundle());
activity.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
LauncherLogProto.ControlType.APP_USAGE_SETTINGS, this);
LauncherLogProto.ControlType.APP_USAGE_SETTINGS, view);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to open app usage settings for task "
+ mTask.getTopComponent().getPackageName(), e);
@@ -203,7 +228,7 @@ public final class DigitalWellBeingToast extends LinearLayout {
private String getContentDescriptionForTask(
Task task, long appUsageLimitTimeMs, long appRemainingTimeMs) {
return appUsageLimitTimeMs >= 0 && appRemainingTimeMs >= 0 ?
getResources().getString(
mActivity.getString(
R.string.task_contents_description_with_remaining_time,
task.titleDescription,
getText(appRemainingTimeMs)) :

View File

@@ -351,6 +351,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
.getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
setWillNotDraw(false);
updateEmptyMessage();
// Initialize quickstep specific cache params here, as this is constructed only once
mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
}
public OverScroller getScroller() {

View File

@@ -27,6 +27,8 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.res.Resources;
@@ -39,6 +41,7 @@ import android.os.Handler;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -145,14 +148,12 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
};
private final TaskOutlineProvider mOutlineProvider;
private final FooterOutlineProvider mFooterOutlineProvider;
private Task mTask;
private TaskThumbnailView mSnapshotView;
private TaskMenuView mMenuView;
private IconView mIconView;
private View mTaskFooterContainer;
private DigitalWellBeingToast mDigitalWellBeingToast;
private final DigitalWellBeingToast mDigitalWellBeingToast;
private float mCurveScale;
private float mFullscreenProgress;
private final FullscreenDrawParams mCurrentFullscreenParams;
@@ -171,6 +172,14 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
private TaskThumbnailCache.ThumbnailLoadRequest mThumbnailLoadRequest;
private TaskIconCache.IconLoadRequest mIconLoadRequest;
// Order in which the footers appear. Lower order appear below higher order.
public static final int INDEX_DIGITAL_WELLBEING_TOAST = 0;
public static final int INDEX_PROACTIVE_SUGGEST = 1;
private final FooterWrapper[] mFooters = new FooterWrapper[2];
private float mFooterVerticalOffset = 0;
private float mFooterAlpha = 1;
private int mStackHeight;
public TaskView(Context context) {
this(context, null);
}
@@ -208,8 +217,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
mCornerRadius = TaskCornerRadius.get(context);
mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius);
mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
mOutlineProvider = new TaskOutlineProvider(getResources(), mCurrentFullscreenParams);
mFooterOutlineProvider = new FooterOutlineProvider(mCurrentFullscreenParams);
setOutlineProvider(mOutlineProvider);
}
@@ -218,10 +228,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
super.onFinishInflate();
mSnapshotView = findViewById(R.id.snapshot);
mIconView = findViewById(R.id.icon);
mDigitalWellBeingToast = findViewById(R.id.digital_well_being_toast);
mTaskFooterContainer = findViewById(R.id.task_footer_container);
mTaskFooterContainer.setOutlineProvider(mFooterOutlineProvider);
mTaskFooterContainer.setClipToOutline(true);
}
public TaskMenuView getMenuView() {
@@ -357,15 +363,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
getRecentsView().updateLiveTileIcon(task.icon);
}
mDigitalWellBeingToast.initialize(
mTask,
contentDescription -> {
setContentDescription(contentDescription);
if (mDigitalWellBeingToast.getVisibility() == VISIBLE
&& getRecentsView() != null) {
getRecentsView().onDigitalWellbeingToastShown();
}
});
mDigitalWellBeingToast.initialize(mTask);
});
} else {
mSnapshotView.setThumbnail(null, null);
@@ -424,14 +422,12 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
mIconView.setScaleX(scale);
mIconView.setScaleY(scale);
int footerVerticalOffset = (int) (mTaskFooterContainer.getHeight() * (1.0f - scale));
mTaskFooterContainer.setTranslationY(
mCurrentFullscreenParams.mCurrentDrawnInsets.bottom +
mCurrentFullscreenParams.mCurrentDrawnInsets.top +
footerVerticalOffset);
mFooterOutlineProvider.setFullscreenDrawParams(
mCurrentFullscreenParams, footerVerticalOffset);
mTaskFooterContainer.invalidateOutline();
mFooterVerticalOffset = 1.0f - scale;
for (FooterWrapper footer : mFooters) {
if (footer != null) {
footer.updateFooterOffset();
}
}
}
public void setIconScaleAnimStartProgress(float startProgress) {
@@ -505,8 +501,13 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
mSnapshotView.setDimAlpha(curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
setCurveScale(getCurveScaleForCurveInterpolation(curveInterpolation));
float fade = Utilities.boundToRange(1.0f - 2 * scrollState.linearInterpolation, 0f, 1f);
mTaskFooterContainer.setAlpha(fade);
mFooterAlpha = Utilities.boundToRange(1.0f - 2 * scrollState.linearInterpolation, 0f, 1f);
for (FooterWrapper footer : mFooters) {
if (footer != null) {
footer.mView.setAlpha(mFooterAlpha);
}
}
if (mMenuView != null) {
mMenuView.setPosition(getX() - getRecentsView().getScrollX(), getY());
mMenuView.setScaleX(getScaleX());
@@ -514,6 +515,56 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
}
}
/**
* Sets the footer at the specific index and returns the previously set footer.
*/
public View setFooter(int index, View view) {
View oldFooter = null;
// If the footer are is already collapsed, do not animate entry
boolean shouldAnimateEntry = mFooterVerticalOffset <= 0;
if (mFooters[index] != null) {
oldFooter = mFooters[index].mView;
mFooters[index].release();
removeView(oldFooter);
// If we are replacing an existing footer, do not animate entry
shouldAnimateEntry = false;
}
if (view != null) {
int indexToAdd = getChildCount();
for (int i = index - 1; i >= 0; i--) {
if (mFooters[i] != null) {
indexToAdd = indexOfChild(mFooters[i].mView);
break;
}
}
addView(view, indexToAdd);
((LayoutParams) view.getLayoutParams()).gravity =
Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
view.setAlpha(mFooterAlpha);
mFooters[index] = new FooterWrapper(view);
if (shouldAnimateEntry) {
mFooters[index].animateEntry();
}
} else {
mFooters[index] = null;
}
mStackHeight = 0;
for (FooterWrapper footer : mFooters) {
if (footer != null) {
footer.setVerticalShift(mStackHeight);
mStackHeight += footer.mExpectedHeight;
}
}
return oldFooter;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
@@ -523,6 +574,18 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(0, 0, getWidth(), getHeight());
setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT);
}
mStackHeight = 0;
for (FooterWrapper footer : mFooters) {
if (footer != null) {
mStackHeight += footer.mView.getHeight();
}
}
for (FooterWrapper footer : mFooters) {
if (footer != null) {
footer.updateFooterOffset();
}
}
}
public static float getCurveScaleForInterpolation(float linearInterpolation) {
@@ -581,26 +644,74 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
}
}
private static final class FooterOutlineProvider extends ViewOutlineProvider {
private class FooterWrapper extends ViewOutlineProvider {
private FullscreenDrawParams mFullscreenDrawParams;
private int mVerticalOffset;
private final Rect mOutlineRect = new Rect();
final View mView;
final ViewOutlineProvider mOldOutlineProvider;
final ViewOutlineProvider mDelegate;
FooterOutlineProvider(FullscreenDrawParams params) {
mFullscreenDrawParams = params;
final int mExpectedHeight;
final int mOldPaddingBottom;
int mAnimationOffset = 0;
int mEntryAnimationOffset = 0;
public FooterWrapper(View view) {
mView = view;
mOldOutlineProvider = view.getOutlineProvider();
mDelegate = mOldOutlineProvider == null
? ViewOutlineProvider.BACKGROUND : mOldOutlineProvider;
int h = view.getLayoutParams().height;
if (h > 0) {
mExpectedHeight = h;
} else {
int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST);
view.measure(m, m);
mExpectedHeight = view.getMeasuredHeight();
}
mOldPaddingBottom = view.getPaddingBottom();
if (mOldOutlineProvider != null) {
view.setOutlineProvider(this);
view.setClipToOutline(true);
}
}
void setFullscreenDrawParams(FullscreenDrawParams params, int verticalOffset) {
mFullscreenDrawParams = params;
mVerticalOffset = verticalOffset;
public void setVerticalShift(int shift) {
mView.setPadding(mView.getPaddingLeft(), mView.getPaddingTop(),
mView.getPaddingRight(), mOldPaddingBottom + shift);
}
@Override
public void getOutline(View view, Outline outline) {
mOutlineRect.set(0, 0, view.getWidth(), view.getHeight());
mOutlineRect.offset(0, -mVerticalOffset);
outline.setRoundRect(mOutlineRect, mFullscreenDrawParams.mCurrentDrawnCornerRadius);
mDelegate.getOutline(view, outline);
outline.offset(0, -mAnimationOffset - mEntryAnimationOffset);
}
void updateFooterOffset() {
mAnimationOffset = Math.round(mStackHeight * mFooterVerticalOffset);
mView.setTranslationY(mAnimationOffset + mEntryAnimationOffset
+ mCurrentFullscreenParams.mCurrentDrawnInsets.bottom
+ mCurrentFullscreenParams.mCurrentDrawnInsets.top);
mView.invalidateOutline();
}
void release() {
mView.setOutlineProvider(mOldOutlineProvider);
setVerticalShift(0);
}
void animateEntry() {
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
animator.addUpdateListener(anim -> {
float factor = 1 - anim.getAnimatedFraction();
int totalShift = mExpectedHeight + mView.getPaddingBottom() - mOldPaddingBottom;
mEntryAnimationOffset = Math.round(factor * totalShift);
updateFooterOffset();
});
animator.setDuration(100);
animator.start();
}
}
@@ -624,7 +735,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
}
}
if (mDigitalWellBeingToast.getVisibility() == VISIBLE) {
if (mDigitalWellBeingToast.hasLimit()) {
info.addAction(
new AccessibilityNodeInfo.AccessibilityAction(
R.string.accessibility_app_usage_settings,
@@ -648,7 +759,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
}
if (action == R.string.accessibility_app_usage_settings) {
mDigitalWellBeingToast.openAppUsageSettings();
mDigitalWellBeingToast.openAppUsageSettings(this);
return true;
}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019 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.
-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/bg_wellbeing_toast"
android:fontFamily="sans-serif"
android:forceHasOverlappingRendering="false"
android:gravity="center"
android:importantForAccessibility="noHideDescendants"
android:textColor="@android:color/white"
android:textSize="14sp"/>

View File

@@ -25,7 +25,7 @@
android:id="@+id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/task_thumbnail_top_margin" />
android:layout_marginTop="@dimen/task_thumbnail_top_margin"/>
<com.android.quickstep.views.IconView
android:id="@+id/icon"
@@ -33,47 +33,5 @@
android:layout_height="@dimen/task_thumbnail_icon_size"
android:layout_gravity="top|center_horizontal"
android:focusable="false"
android:importantForAccessibility="no" />
<LinearLayout
android:id="@+id/task_footer_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:animateLayoutChanges="true"
android:forceHasOverlappingRendering="true"
android:layout_gravity="bottom|center_horizontal">
<FrameLayout
android:id="@+id/proactive_suggest_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:visibility="gone"
/>
<com.android.quickstep.views.DigitalWellBeingToast
android:id="@+id/digital_well_being_toast"
android:layout_width="match_parent"
android:layout_height="48dp"
android:importantForAccessibility="noHideDescendants"
android:background="@drawable/bg_wellbeing_toast"
android:gravity="center"
android:visibility="gone">
<ImageView
android:id="@+id/digital_well_being_hourglass"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_hourglass_top"
/>
<TextView
android:id="@+id/digital_well_being_remaining_time"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:fontFamily="sans-serif"
android:textSize="14sp"
android:textColor="@android:color/white"
android:gravity="center_vertical"
/>
</com.android.quickstep.views.DigitalWellBeingToast>
</LinearLayout>
android:importantForAccessibility="no"/>
</com.android.quickstep.views.TaskView>

View File

@@ -51,15 +51,15 @@ public class DigitalWellBeingToastTest extends AbstractQuickStepTest {
mLauncher.pressHome();
final DigitalWellBeingToast toast = getToast();
assertTrue("Toast is not visible", toast.isShown());
assertEquals("Toast text: ", "5 minutes left today", toast.getTextView().getText());
assertTrue("Toast is not visible", toast.hasLimit());
assertEquals("Toast text: ", "5 minutes left today", toast.getText());
// Unset time limit for app.
runWithShellPermission(
() -> usageStatsManager.unregisterAppUsageLimitObserver(observerId));
mLauncher.pressHome();
assertFalse("Toast is visible", getToast().isShown());
assertFalse("Toast is visible", getToast().hasLimit());
} finally {
runWithShellPermission(
() -> usageStatsManager.unregisterAppUsageLimitObserver(observerId));

View File

@@ -69,6 +69,7 @@ import com.android.launcher3.compat.ShortcutConfigActivityInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.graphics.TintedDrawableSpan;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutKey;
@@ -562,6 +563,20 @@ public final class Utilities {
return spanned;
}
/**
* Prefixes a text with the provided icon
*/
public static CharSequence prefixTextWithIcon(Context context, int iconRes, CharSequence msg) {
// Update the hint to contain the icon.
// Prefix the original hint with two spaces. The first space gets replaced by the icon
// using span. The second space is used for a singe space character between the hint
// and the icon.
SpannableString spanned = new SpannableString(" " + msg);
spanned.setSpan(new TintedDrawableSpan(context, iconRes),
0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
return spanned;
}
public static SharedPreferences getPrefs(Context context) {
return context.getSharedPreferences(
LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE);

View File

@@ -20,6 +20,7 @@ import static android.view.View.MeasureSpec.getSize;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static com.android.launcher3.LauncherState.ALL_APPS_HEADER;
import static com.android.launcher3.Utilities.prefixTextWithIcon;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import android.content.Context;
@@ -40,6 +41,7 @@ import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AlphabeticalAppsList;
@@ -89,14 +91,7 @@ public class AppsSearchContainerLayout extends ExtendedEditText
mFixedTranslationY = getTranslationY();
mMarginTopAdjusting = mFixedTranslationY - getPaddingTop();
// Update the hint to contain the icon.
// Prefix the original hint with two spaces. The first space gets replaced by the icon
// using span. The second space is used for a singe space character between the hint
// and the icon.
SpannableString spanned = new SpannableString(" " + getHint());
spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search),
0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
setHint(spanned);
setHint(prefixTextWithIcon(getContext(), R.drawable.ic_allapps_search, getHint()));
}
@Override

View File

@@ -32,7 +32,7 @@ public class TintedDrawableSpan extends DynamicDrawableSpan {
public TintedDrawableSpan(Context context, int resourceId) {
super(ALIGN_BOTTOM);
mDrawable = context.getDrawable(resourceId);
mDrawable = context.getDrawable(resourceId).mutate();
mOldTint = 0;
mDrawable.setTint(0);
}