Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a5dcf9e3 authored by Jason Hsu's avatar Jason Hsu Committed by Android (Google) Code Review
Browse files

Merge "[Drag To Hide] Shows notification when user drag-to-hide a11y floating button" into main

parents 64353234 72fa82d5
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -9,6 +9,13 @@ flag {
    bug: "297556899"
}

flag {
    name: "floating_menu_drag_to_hide"
    namespace: "accessibility"
    description: "Allows users to hide the FAB then use notification to dismiss or bring it back."
    bug: "298718415"
}

flag {
    name: "floating_menu_ime_displacement_animation"
    namespace: "accessibility"
+7 −0
Original line number Diff line number Diff line
@@ -2284,6 +2284,8 @@
    <string name="notification_channel_storage">Storage</string>
    <!-- Title for the notification channel for hints and suggestions. [CHAR LIMIT=NONE] -->
    <string name="notification_channel_hints">Hints</string>
    <!-- Title for the notification channel for accessibility related (i.e. accessibility floating menu). [CHAR LIMIT=NONE] -->
    <string name="notification_channel_accessibility">Accessibility</string>

    <!-- App label of the instant apps notification [CHAR LIMIT=60] -->
    <string name="instant_apps">Instant Apps</string>
@@ -2554,6 +2556,11 @@
    <string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string>
    <!-- Text for the undo action button of the message view of the accessibility floating menu to perform undo operation. [CHAR LIMIT=30]-->
    <string name="accessibility_floating_button_undo">Undo</string>
    <!-- Notification title shown when accessibility floating button is in hidden state. [CHAR LIMIT=NONE] -->
    <string name="accessibility_floating_button_hidden_notification_title">Accessibility button hidden</string>
    <!-- Notification content text to explain user can tap notification to bring back accessibility floating button. [CHAR LIMIT=NONE] -->
    <string name="accessibility_floating_button_hidden_notification_text">Tap to show accessibility button</string>

    <!-- Text for the message view with undo action of the accessibility floating menu to show which feature shortcut was removed. [CHAR LIMIT=30]-->
    <string name="accessibility_floating_button_undo_message_label_text"><xliff:g id="feature name" example="Magnification">%s</xliff:g> shortcut removed</string>
    <!-- Text for the message view with undo action of the accessibility floating menu to show how many features shortcuts were removed. [CHAR LIMIT=30]-->
+6 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.dynamicanimation.animation.DynamicAnimation;

import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.common.bubbles.DismissView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;

@@ -116,6 +117,11 @@ class DragToInteractAnimationController {
        mMagnetizedObject.setMagnetListener(magnetListener);
    }

    @VisibleForTesting
    MagnetizedObject.MagnetListener getMagnetListener() {
        return mMagnetizedObject.getMagnetListener();
    }

    void maybeConsumeDownMotionEvent(MotionEvent event) {
        mMagnetizedObject.maybeConsumeMotionEvent(event);
    }
+75 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

package com.android.systemui.accessibility.floatingmenu;

import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;

import com.android.systemui.res.R;
import com.android.systemui.util.NotificationChannels;

class MenuNotificationFactory {
    public static final String ACTION_UNDO =
            "com.android.systemui.accessibility.floatingmenu.action.UNDO";
    public static final String ACTION_DELETE =
            "com.android.systemui.accessibility.floatingmenu.action.DELETE";

    private final Context mContext;

    MenuNotificationFactory(Context context) {
        mContext = context;
    }

    public Notification createHiddenNotification() {
        final CharSequence title = mContext.getText(
                R.string.accessibility_floating_button_hidden_notification_title);
        final CharSequence content = mContext.getText(
                R.string.accessibility_floating_button_hidden_notification_text);

        return new Notification.Builder(mContext, NotificationChannels.ALERTS)
                .setContentTitle(title)
                .setContentText(content)
                .setSmallIcon(R.drawable.ic_settings_24dp)
                .setContentIntent(buildUndoIntent())
                .setDeleteIntent(buildDeleteIntent())
                .setColor(mContext.getResources().getColor(
                        com.android.internal.R.color.system_notification_accent_color))
                .setLocalOnly(true)
                .setCategory(Notification.CATEGORY_SYSTEM)
                .build();
    }

    private PendingIntent buildUndoIntent() {
        final Intent intent = new Intent(ACTION_UNDO);

        return PendingIntent.getBroadcast(mContext, /* requestCode= */ 0, intent,
                PendingIntent.FLAG_IMMUTABLE);

    }

    private PendingIntent buildDeleteIntent() {
        final Intent intent = new Intent(ACTION_DELETE);

        return PendingIntent.getBroadcastAsUser(mContext, /* requestCode= */ 0, intent,
                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
                        | PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT);

    }
}
+102 −12
Original line number Diff line number Diff line
@@ -26,16 +26,21 @@ import static com.android.internal.accessibility.common.ShortcutConstants.Access
import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_DELETE;
import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_UNDO;
import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.StringDef;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -58,6 +63,7 @@ import androidx.lifecycle.Observer;

import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.util.Preconditions;
import com.android.systemui.Flags;
import com.android.systemui.res.R;
@@ -91,6 +97,8 @@ class MenuViewLayer extends FrameLayout implements
    private final MenuViewAppearance mMenuViewAppearance;
    private final MenuAnimationController mMenuAnimationController;
    private final AccessibilityManager mAccessibilityManager;
    private final NotificationManager mNotificationManager;
    private final MenuNotificationFactory mNotificationFactory;
    private final Handler mHandler = new Handler(Looper.getMainLooper());
    private final IAccessibilityFloatingMenu mFloatingMenu;
    private final SecureSettings mSecureSettings;
@@ -103,7 +111,9 @@ class MenuViewLayer extends FrameLayout implements
    private final Rect mImeInsetsRect = new Rect();
    private boolean mIsMigrationTooltipShowing;
    private boolean mShouldShowDockTooltip;
    private boolean mIsNotificationShown;
    private Optional<MenuEduTooltipView> mEduTooltipView = Optional.empty();
    private BroadcastReceiver mNotificationActionReceiver;

    @IntDef({
            LayerIndex.MENU_VIEW,
@@ -184,10 +194,16 @@ class MenuViewLayer extends FrameLayout implements
        mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
        mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance);
        mMenuAnimationController = mMenuView.getMenuAnimationController();
        if (Flags.floatingMenuDragToHide()) {
            mMenuAnimationController.setDismissCallback(this::hideMenuAndShowNotification);
        } else {
            mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
        }
        mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
        mDismissView = new DismissView(context);
        DismissViewUtils.setup(mDismissView);
        mNotificationFactory = new MenuNotificationFactory(context);
        mNotificationManager = context.getSystemService(NotificationManager.class);
        mDragToInteractAnimationController = new DragToInteractAnimationController(
                mDismissView, mMenuView);
        mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
@@ -204,7 +220,11 @@ class MenuViewLayer extends FrameLayout implements

            @Override
            public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
                if (Flags.floatingMenuDragToHide()) {
                    hideMenuAndShowNotification();
                } else {
                    hideMenuAndShowMessage();
                }
                mDismissView.hide();
                mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
            }
@@ -218,18 +238,25 @@ class MenuViewLayer extends FrameLayout implements
        mMessageView = new MenuMessageView(context);

        mMenuView.setOnTargetFeaturesChangeListener(newTargetFeatures -> {
            if (Flags.floatingMenuDragToHide()) {
                dismissNotification();
                undo();
            } else {
                if (newTargetFeatures.size() < 1) {
                    return;
                }

                // During the undo action period, the pending action will be canceled and undo back
            // to the previous state if users did any action related to the accessibility features.
                // to the previous state if users did any action related to the accessibility
                // features.
                if (mMessageView.getVisibility() == VISIBLE) {
                    undo();
                }


                final TextView messageText = (TextView) mMessageView.getChildAt(Index.TEXT_VIEW);
                messageText.setText(getMessageText(newTargetFeatures));
            }
        });

        addView(mMenuView, LayerIndex.MENU_VIEW);
@@ -456,6 +483,50 @@ class MenuViewLayer extends FrameLayout implements
        mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
    }

    private void hideMenuAndShowNotification() {
        mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
        showNotification();
    }

    private void showNotification() {
        registerReceiverIfNeeded();
        if (!mIsNotificationShown) {
            mNotificationManager.notify(
                    SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN,
                    mNotificationFactory.createHiddenNotification());
            mIsNotificationShown = true;
        }
    }

    private void dismissNotification() {
        unregisterReceiverIfNeeded();
        if (mIsNotificationShown) {
            mNotificationManager.cancel(
                    SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN);
            mIsNotificationShown = false;
        }
    }

    private void registerReceiverIfNeeded() {
        if (mNotificationActionReceiver != null) {
            return;
        }
        mNotificationActionReceiver = new MenuNotificationActionReceiver();
        final IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ACTION_UNDO);
        intentFilter.addAction(ACTION_DELETE);
        getContext().registerReceiver(mNotificationActionReceiver, intentFilter,
                Context.RECEIVER_EXPORTED);
    }

    private void unregisterReceiverIfNeeded() {
        if (mNotificationActionReceiver == null) {
            return;
        }
        getContext().unregisterReceiver(mNotificationActionReceiver);
        mNotificationActionReceiver = null;
    }

    private void undo() {
        mHandler.removeCallbacksAndMessages(/* token= */ null);
        mMessageView.setVisibility(GONE);
@@ -464,4 +535,23 @@ class MenuViewLayer extends FrameLayout implements
        mMenuView.setVisibility(VISIBLE);
        mMenuAnimationController.startGrowAnimation();
    }

    @VisibleForTesting
    DragToInteractAnimationController getDragToInteractAnimationController() {
        return mDragToInteractAnimationController;
    }

    private class MenuNotificationActionReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (ACTION_UNDO.equals(action)) {
                dismissNotification();
                undo();
            } else if (ACTION_DELETE.equals(action)) {
                dismissNotification();
                mDismissMenuAction.run();
            }
        }
    }
}
Loading