| /** |
| * 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. |
| */ |
| |
| package com.android.launcher3.appprediction; |
| |
| import static com.android.launcher3.LauncherState.ALL_APPS; |
| import static com.android.quickstep.logging.UserEventDispatcherExtension.ALL_APPS_PREDICTION_TIPS; |
| |
| import android.content.Context; |
| import android.graphics.CornerPathEffect; |
| import android.graphics.Paint; |
| import android.graphics.drawable.ShapeDrawable; |
| import android.os.Handler; |
| import android.os.UserManager; |
| import android.util.AttributeSet; |
| import android.util.TypedValue; |
| import android.view.Gravity; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.LinearLayout; |
| import android.widget.TextView; |
| |
| import androidx.core.content.ContextCompat; |
| |
| import com.android.launcher3.AbstractFloatingView; |
| import com.android.launcher3.Launcher; |
| import com.android.launcher3.LauncherState; |
| import com.android.launcher3.LauncherStateManager; |
| import com.android.launcher3.R; |
| import com.android.launcher3.Utilities; |
| import com.android.launcher3.allapps.FloatingHeaderView; |
| import com.android.launcher3.anim.Interpolators; |
| import com.android.launcher3.dragndrop.DragLayer; |
| import com.android.launcher3.graphics.TriangleShape; |
| import com.android.systemui.shared.system.LauncherEventUtil; |
| |
| /** |
| * All apps tip view aligned just above prediction apps, shown to users that enter all apps for the |
| * first time. |
| */ |
| public class AllAppsTipView extends AbstractFloatingView { |
| |
| private static final String ALL_APPS_TIP_SEEN = "launcher.all_apps_tip_seen"; |
| private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000; |
| private static final long SHOW_DELAY_MS = 200; |
| private static final long SHOW_DURATION_MS = 300; |
| private static final long HIDE_DURATION_MS = 100; |
| |
| private final Launcher mLauncher; |
| private final Handler mHandler = new Handler(); |
| |
| private AllAppsTipView(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| private AllAppsTipView(Context context, AttributeSet attrs, int defStyleAttr) { |
| super(context, attrs, defStyleAttr); |
| setOrientation(LinearLayout.VERTICAL); |
| |
| mLauncher = Launcher.getLauncher(context); |
| |
| init(context); |
| } |
| |
| @Override |
| public boolean onControllerInterceptTouchEvent(MotionEvent ev) { |
| if (ev.getAction() == MotionEvent.ACTION_DOWN) { |
| close(true); |
| } |
| return false; |
| } |
| |
| @Override |
| protected void handleClose(boolean animate) { |
| if (mIsOpen) { |
| if (animate) { |
| animate().alpha(0f) |
| .withLayer() |
| .setStartDelay(0) |
| .setDuration(HIDE_DURATION_MS) |
| .setInterpolator(Interpolators.ACCEL) |
| .withEndAction(() -> mLauncher.getDragLayer().removeView(this)) |
| .start(); |
| } else { |
| animate().cancel(); |
| mLauncher.getDragLayer().removeView(this); |
| } |
| mLauncher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply(); |
| mIsOpen = false; |
| } |
| } |
| |
| @Override |
| public void logActionCommand(int command) { |
| } |
| |
| @Override |
| protected boolean isOfType(int type) { |
| return (type & TYPE_ON_BOARD_POPUP) != 0; |
| } |
| |
| private void init(Context context) { |
| inflate(context, R.layout.arrow_toast, this); |
| |
| TextView textView = findViewById(R.id.text); |
| textView.setText(R.string.all_apps_prediction_tip); |
| |
| View dismissButton = findViewById(R.id.dismiss); |
| dismissButton.setOnClickListener(view -> { |
| mLauncher.getUserEventDispatcher().logActionTip( |
| LauncherEventUtil.DISMISS, ALL_APPS_PREDICTION_TIPS); |
| handleClose(true); |
| }); |
| |
| View arrowView = findViewById(R.id.arrow); |
| ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams(); |
| ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create( |
| arrowLp.width, arrowLp.height, false)); |
| Paint arrowPaint = arrowDrawable.getPaint(); |
| TypedValue typedValue = new TypedValue(); |
| context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true); |
| arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId)); |
| // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable. |
| arrowPaint.setPathEffect(new CornerPathEffect( |
| context.getResources().getDimension(R.dimen.arrow_toast_corner_radius))); |
| arrowView.setBackground(arrowDrawable); |
| |
| mIsOpen = true; |
| |
| mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS); |
| } |
| |
| private static boolean showAllAppsTipIfNecessary(Launcher launcher) { |
| FloatingHeaderView floatingHeaderView = launcher.getAppsView().getFloatingHeaderView(); |
| if (!floatingHeaderView.hasVisibleContent() |
| || AbstractFloatingView.getOpenView(launcher, |
| TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE) != null |
| || !launcher.isInState(ALL_APPS) |
| || hasSeenAllAppsTip(launcher) |
| || launcher.getSystemService(UserManager.class).isDemoUser() |
| || Utilities.IS_RUNNING_IN_TEST_HARNESS) { |
| return false; |
| } |
| |
| AllAppsTipView allAppsTipView = new AllAppsTipView(launcher.getAppsView().getContext(), |
| null); |
| launcher.getDragLayer().addView(allAppsTipView); |
| |
| DragLayer.LayoutParams params = (DragLayer.LayoutParams) allAppsTipView.getLayoutParams(); |
| params.gravity = Gravity.CENTER_HORIZONTAL; |
| |
| int top = floatingHeaderView.findFixedRowByType(PredictionRowView.class).getTop(); |
| allAppsTipView.setY(top - launcher.getResources().getDimensionPixelSize( |
| R.dimen.all_apps_tip_bottom_margin)); |
| |
| allAppsTipView.setAlpha(0); |
| allAppsTipView.animate() |
| .alpha(1f) |
| .withLayer() |
| .setStartDelay(SHOW_DELAY_MS) |
| .setDuration(SHOW_DURATION_MS) |
| .setInterpolator(Interpolators.DEACCEL) |
| .start(); |
| |
| launcher.getUserEventDispatcher().logActionTip( |
| LauncherEventUtil.VISIBLE, ALL_APPS_PREDICTION_TIPS); |
| return true; |
| } |
| |
| private static boolean hasSeenAllAppsTip(Launcher launcher) { |
| return launcher.getSharedPrefs().getBoolean(ALL_APPS_TIP_SEEN, false); |
| } |
| |
| public static void scheduleShowIfNeeded(Launcher launcher) { |
| if (!hasSeenAllAppsTip(launcher)) { |
| launcher.getStateManager().addStateListener( |
| new LauncherStateManager.StateListener() { |
| @Override |
| public void onStateTransitionStart(LauncherState toState) { |
| } |
| |
| @Override |
| public void onStateTransitionComplete(LauncherState finalState) { |
| if (finalState == ALL_APPS) { |
| if (showAllAppsTipIfNecessary(launcher)) { |
| launcher.getStateManager().removeStateListener(this); |
| } |
| } |
| } |
| }); |
| } |
| } |
| } |