| /* |
| * 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; |
| |
| import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; |
| import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON; |
| import static com.android.launcher3.LauncherState.ALL_APPS; |
| import static com.android.launcher3.LauncherState.NORMAL; |
| import static com.android.launcher3.LauncherState.OVERVIEW; |
| import static com.android.launcher3.allapps.DiscoveryBounce.BOUNCE_MAX_COUNT; |
| import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_COUNT; |
| import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN; |
| import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT; |
| import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN; |
| |
| import android.animation.AnimatorSet; |
| import android.animation.ValueAnimator; |
| import android.content.Intent; |
| import android.content.IntentSender; |
| import android.os.Bundle; |
| import android.os.CancellationSignal; |
| |
| import com.android.launcher3.LauncherState.ScaleAndTranslation; |
| import com.android.launcher3.LauncherStateManager.StateHandler; |
| import com.android.launcher3.model.WellbeingModel; |
| import com.android.launcher3.popup.SystemShortcut; |
| import com.android.launcher3.proxy.ProxyActivityStarter; |
| import com.android.launcher3.proxy.StartActivityParams; |
| import com.android.launcher3.uioverrides.BackButtonAlphaHandler; |
| import com.android.launcher3.uioverrides.RecentsViewStateController; |
| import com.android.launcher3.util.UiThreadHelper; |
| import com.android.quickstep.RecentsModel; |
| import com.android.quickstep.SysUINavigationMode; |
| import com.android.quickstep.SysUINavigationMode.Mode; |
| import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; |
| import com.android.quickstep.SystemUiProxy; |
| import com.android.quickstep.util.RemoteFadeOutAnimationListener; |
| import com.android.quickstep.util.ShelfPeekAnim; |
| |
| import java.util.stream.Stream; |
| |
| /** |
| * Extension of Launcher activity to provide quickstep specific functionality |
| */ |
| public abstract class BaseQuickstepLauncher extends Launcher |
| implements NavigationModeChangeListener { |
| |
| /** |
| * Reusable command for applying the back button alpha on the background thread. |
| */ |
| public static final UiThreadHelper.AsyncCommand SET_BACK_BUTTON_ALPHA = |
| (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha( |
| Float.intBitsToFloat(arg1), arg2 != 0); |
| |
| private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this); |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| |
| SysUINavigationMode.Mode mode = SysUINavigationMode.INSTANCE.get(this) |
| .addModeChangeListener(this); |
| getRotationHelper().setRotationHadDifferentUI(mode != Mode.NO_BUTTON); |
| |
| if (!getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) { |
| getStateManager().addStateListener(new LauncherStateManager.StateListener() { |
| @Override |
| public void onStateTransitionStart(LauncherState toState) { } |
| |
| @Override |
| public void onStateTransitionComplete(LauncherState finalState) { |
| boolean swipeUpEnabled = SysUINavigationMode.INSTANCE |
| .get(BaseQuickstepLauncher.this).getMode().hasGestures; |
| LauncherState prevState = getStateManager().getLastState(); |
| |
| if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled |
| && finalState == ALL_APPS && prevState == NORMAL) || BOUNCE_MAX_COUNT |
| <= getSharedPrefs().getInt(HOME_BOUNCE_COUNT, 0))) { |
| getSharedPrefs().edit().putBoolean(HOME_BOUNCE_SEEN, true).apply(); |
| getStateManager().removeStateListener(this); |
| } |
| } |
| }); |
| } |
| |
| if (!getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)) { |
| getStateManager().addStateListener(new LauncherStateManager.StateListener() { |
| @Override |
| public void onStateTransitionStart(LauncherState toState) { } |
| |
| @Override |
| public void onStateTransitionComplete(LauncherState finalState) { |
| LauncherState prevState = getStateManager().getLastState(); |
| |
| if ((finalState == ALL_APPS && prevState == OVERVIEW) || BOUNCE_MAX_COUNT |
| <= getSharedPrefs().getInt(SHELF_BOUNCE_COUNT, 0)) { |
| getSharedPrefs().edit().putBoolean(SHELF_BOUNCE_SEEN, true).apply(); |
| getStateManager().removeStateListener(this); |
| } |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void onDestroy() { |
| SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this); |
| super.onDestroy(); |
| } |
| |
| @Override |
| public void onNavigationModeChanged(Mode newMode) { |
| getDragLayer().recreateControllers(); |
| getRotationHelper().setRotationHadDifferentUI(newMode != Mode.NO_BUTTON); |
| } |
| |
| @Override |
| public void onEnterAnimationComplete() { |
| super.onEnterAnimationComplete(); |
| // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled |
| // as a part of quickstep, so that high-res thumbnails can load the next time we enter |
| // overview |
| RecentsModel.INSTANCE.get(this).getThumbnailCache() |
| .getHighResLoadingState().setVisible(true); |
| } |
| |
| @Override |
| public void onTrimMemory(int level) { |
| super.onTrimMemory(level); |
| RecentsModel.INSTANCE.get(this).onTrimMemory(level); |
| } |
| |
| @Override |
| public void startIntentSenderForResult(IntentSender intent, int requestCode, |
| Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) { |
| if (requestCode != -1) { |
| mPendingActivityRequestCode = requestCode; |
| StartActivityParams params = new StartActivityParams(this, requestCode); |
| params.intentSender = intent; |
| params.fillInIntent = fillInIntent; |
| params.flagsMask = flagsMask; |
| params.flagsValues = flagsValues; |
| params.extraFlags = extraFlags; |
| params.options = options; |
| startActivity(ProxyActivityStarter.getLaunchIntent(this, params)); |
| } else { |
| super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, |
| flagsValues, extraFlags, options); |
| } |
| } |
| |
| @Override |
| public void startActivityForResult(Intent intent, int requestCode, Bundle options) { |
| if (requestCode != -1) { |
| mPendingActivityRequestCode = -1; |
| StartActivityParams params = new StartActivityParams(this, requestCode); |
| params.intent = intent; |
| params.options = options; |
| startActivity(ProxyActivityStarter.getLaunchIntent(this, params)); |
| } else { |
| super.startActivityForResult(intent, requestCode, options); |
| } |
| } |
| |
| @Override |
| protected void onDeferredResumed() { |
| if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) { |
| // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher. |
| onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null); |
| // ProxyActivityStarter is started with clear task to reset the task after which it |
| // removes the task itself. |
| startActivity(ProxyActivityStarter.getLaunchIntent(this, null)); |
| } |
| } |
| |
| @Override |
| protected StateHandler[] createStateHandlers() { |
| return new StateHandler[] { |
| getAllAppsController(), |
| getWorkspace(), |
| new RecentsViewStateController(this), |
| new BackButtonAlphaHandler(this)}; |
| } |
| |
| @Override |
| protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() { |
| if (SysUINavigationMode.getMode(this) == Mode.NO_BUTTON) { |
| float offscreenTranslationX = getDeviceProfile().widthPx |
| - getOverviewPanel().getPaddingStart(); |
| return new ScaleAndTranslation(1f, offscreenTranslationX, 0f); |
| } |
| return super.getOverviewScaleAndTranslationForNormalState(); |
| } |
| |
| @Override |
| public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { |
| QuickstepAppTransitionManagerImpl appTransitionManager = |
| (QuickstepAppTransitionManagerImpl) getAppTransitionManager(); |
| appTransitionManager.setRemoteAnimationProvider((appTargets, wallpaperTargets) -> { |
| |
| // On the first call clear the reference. |
| signal.cancel(); |
| |
| ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0); |
| fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets, |
| wallpaperTargets)); |
| AnimatorSet anim = new AnimatorSet(); |
| anim.play(fadeAnimation); |
| return anim; |
| }, signal); |
| } |
| |
| @Override |
| public void onDragLayerHierarchyChanged() { |
| onLauncherStateOrFocusChanged(); |
| } |
| |
| @Override |
| protected void onActivityFlagsChanged(int changeBits) { |
| if ((changeBits |
| & (ACTIVITY_STATE_WINDOW_FOCUSED | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) { |
| onLauncherStateOrFocusChanged(); |
| } |
| |
| super.onActivityFlagsChanged(changeBits); |
| } |
| |
| /** |
| * Sets the back button visibility based on the current state/window focus. |
| */ |
| private void onLauncherStateOrFocusChanged() { |
| Mode mode = SysUINavigationMode.getMode(this); |
| boolean shouldBackButtonBeHidden = mode.hasGestures |
| && getStateManager().getState().hideBackButton |
| && hasWindowFocus() |
| && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0; |
| if (shouldBackButtonBeHidden) { |
| // Show the back button if there is a floating view visible. |
| shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(this, |
| TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null; |
| } |
| UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA, |
| shouldBackButtonBeHidden ? 0f : 1f, true /* animate */); |
| if (getDragLayer() != null) { |
| getRootView().setDisallowBackGesture(shouldBackButtonBeHidden); |
| } |
| } |
| |
| @Override |
| public void finishBindingItems(int pageBoundFirst) { |
| super.finishBindingItems(pageBoundFirst); |
| // Instantiate and initialize WellbeingModel now that its loading won't interfere with |
| // populating workspace. |
| // TODO: Find a better place for this |
| WellbeingModel.get(this); |
| } |
| |
| @Override |
| public Stream<SystemShortcut.Factory> getSupportedShortcuts() { |
| return Stream.concat(super.getSupportedShortcuts(), |
| Stream.of(WellbeingModel.SHORTCUT_FACTORY)); |
| } |
| |
| public ShelfPeekAnim getShelfPeekAnim() { |
| return mShelfPeekAnim; |
| } |
| } |