onUserInteraction not working in DialogPreference
Asked Answered
E

4

11

It is expected that onUserInteraction is being called for any user interaction. it works fine in PreferenceActivity. However, when a DialogPreference is popup, onUserInteraction is not called anymore even there is user interaction such as touch event.

It seems that DialogPreference is not the only case. Whenever Dialog is shown, it does not report the user interaction to activity.

But what can I do if I really need it. Thank You.

Ejector answered 12/2, 2013 at 9:0 Comment(0)
C
11

As far as I know, the onUserInteraction() is simply not called while the user is interacting with a dialog (even started from Activity in which you're monitoring interactions).

Two solutions I know are:

  • Subclass Dialog/DialogPreference class and override dispatchTouchEvent().

  • Implement Window.Callback interface and set it as Dialogs window callback by issuing:

    dialog.getWindow().setCallback(callbackImplementation);
    

    Note: this implementation should process all received events by calling appropriate dialog methods or handle the events in your own way (e.g. by manually calling onUserInteraction()).

Edit

You have couple of ways to get Activity from the custom PreferenceDialog instance.

  1. Call DialogPreference.getPreferenceManager() method which returns PreferenceManager. It has a getActivity() method but it's package-private so you would have to put your custom DialogPreference in android.preference package to access it.

  2. In the PreferenceActivity.onCreate(), after inflating the preferences, use findPreference() to find your custom DialogPreference by key. Then cast it to your custom class and set activity to this via an accessor.

I would go with the second option.

Choplogic answered 12/2, 2013 at 9:32 Comment(3)
But how to get the attached activity in DialogPreference?Ejector
Thanks for your answer. But is there a way to extends the DialogPreference such that I don't need to setActivity everytime I use it? It seems that the only way is to use reflection to call getActivity in the custom subclass of DialogPreference...Ejector
looking at the code - I don't see another reliable and clean way. I'd stick to the setter as that is a more clean solution. but as always, the decision is yours :)Choplogic
P
9

Here is a complete solution for a DialogFragment, which triggers Activity's onUserInteraction() on a touch and preserves default callback's behavior:

public abstract class BaseDialogFragment extends DialogFragment {

    @Override
    public void onActivityCreated(final Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        final Window window = getDialog().getWindow();
        if (window != null) {
            window.setCallback(new UserInteractionAwareCallback(window.getCallback(), getActivity()));
        }
    }
}

And here is the Callback itself:

public class UserInteractionAwareCallback implements Window.Callback {

    private final Window.Callback originalCallback;
    private final Activity activity;

    public UserInteractionAwareCallback(final Window.Callback originalCallback, final Activity activity) {
        this.originalCallback = originalCallback;
        this.activity = activity;
    }

    @Override
    public boolean dispatchKeyEvent(final KeyEvent event) {
        return originalCallback.dispatchKeyEvent(event);
    }

    @Override
    public boolean dispatchKeyShortcutEvent(final KeyEvent event) {
        return originalCallback.dispatchKeyShortcutEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(final MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
                if (activity != null) {
                    activity.onUserInteraction();
                }
                break;
            default:
        }
        return originalCallback.dispatchTouchEvent(event);
    }

    @Override
    public boolean dispatchTrackballEvent(final MotionEvent event) {
        return originalCallback.dispatchTrackballEvent(event);
    }

    @Override
    public boolean dispatchGenericMotionEvent(final MotionEvent event) {
        return originalCallback.dispatchGenericMotionEvent(event);
    }

    @Override
    public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent event) {
        return originalCallback.dispatchPopulateAccessibilityEvent(event);
    }

    @Nullable
    @Override
    public View onCreatePanelView(final int featureId) {
        return originalCallback.onCreatePanelView(featureId);
    }

    @Override
    public boolean onCreatePanelMenu(final int featureId, final Menu menu) {
        return originalCallback.onCreatePanelMenu(featureId, menu);
    }

    @Override
    public boolean onPreparePanel(final int featureId, final View view, final Menu menu) {
        return originalCallback.onPreparePanel(featureId, view, menu);
    }

    @Override
    public boolean onMenuOpened(final int featureId, final Menu menu) {
        return originalCallback.onMenuOpened(featureId, menu);
    }

    @Override
    public boolean onMenuItemSelected(final int featureId, final MenuItem item) {
        return originalCallback.onMenuItemSelected(featureId, item);
    }

    @Override
    public void onWindowAttributesChanged(final WindowManager.LayoutParams attrs) {
        originalCallback.onWindowAttributesChanged(attrs);
    }

    @Override
    public void onContentChanged() {
        originalCallback.onContentChanged();
    }

    @Override
    public void onWindowFocusChanged(final boolean hasFocus) {
        originalCallback.onWindowFocusChanged(hasFocus);
    }

    @Override
    public void onAttachedToWindow() {
        originalCallback.onAttachedToWindow();
    }

    @Override
    public void onDetachedFromWindow() {
        originalCallback.onDetachedFromWindow();
    }

    @Override
    public void onPanelClosed(final int featureId, final Menu menu) {
        originalCallback.onPanelClosed(featureId, menu);
    }

    @Override
    public boolean onSearchRequested() {
        return originalCallback.onSearchRequested();
    }

    @TargetApi(Build.VERSION_CODES.M)
    @Override
    public boolean onSearchRequested(final SearchEvent searchEvent) {
        return originalCallback.onSearchRequested(searchEvent);
    }

    @Nullable
    @Override
    public ActionMode onWindowStartingActionMode(final ActionMode.Callback callback) {
        return originalCallback.onWindowStartingActionMode(callback);
    }

    @TargetApi(Build.VERSION_CODES.M)
    @Nullable
    @Override
    public ActionMode onWindowStartingActionMode(final ActionMode.Callback callback, final int type) {
        return originalCallback.onWindowStartingActionMode(callback, type);
    }

    @Override
    public void onActionModeStarted(final ActionMode mode) {
        originalCallback.onActionModeStarted(mode);
    }

    @Override
    public void onActionModeFinished(final ActionMode mode) {
        originalCallback.onActionModeFinished(mode);
    }
}
Pyretotherapy answered 1/6, 2018 at 16:8 Comment(0)
W
5

Here's a more self-contained and more complete Kotlin implementation:

/**
 * Sets up the receiver's [window][Dialog.getWindow] to call [Activity.onUserInteraction]
 * at appropriate times, mirroring the calls made in [Activity] itself.
 * This method should be called immediately after [Dialog.show].
 */
fun Dialog.reportUserInteraction() {
  window?.let { window ->
    val activity = window.decorView.activity

    val wrappedCallback = window.callback
    window.callback = object : Window.Callback by wrappedCallback {
      override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
        activity.onUserInteraction()
        return wrappedCallback.dispatchGenericMotionEvent(event)
      }

      override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        activity.onUserInteraction()
        return wrappedCallback.dispatchKeyEvent(event)
      }

      override fun dispatchKeyShortcutEvent(event: KeyEvent): Boolean {
        activity.onUserInteraction()
        return wrappedCallback.dispatchKeyShortcutEvent(event)
      }

      override fun dispatchTouchEvent(event: MotionEvent): Boolean {
        if (event.action == ACTION_DOWN) activity.onUserInteraction()
        return wrappedCallback.dispatchTouchEvent(event)
      }

      override fun dispatchTrackballEvent(event: MotionEvent): Boolean {
        activity.onUserInteraction()
        return wrappedCallback.dispatchTrackballEvent(event)
      }
    }
  }
}

Relies on:

val View.activity: Activity
  get() = context.activityOrNull!!

val Context.activityOrNull: Activity?
  get() {
    var context = this
    while (true) {
      if (context is Application) {
        return null
      }
      if (context is Activity) {
        return context
      }
      if (context is ContextWrapper) {
        val baseContext = context.baseContext
        // Prevent Stack Overflow.
        if (baseContext === this) {
          return null
        }
        context = baseContext
      } else {
        return null
      }
    }
  }
Woke answered 8/2, 2021 at 22:40 Comment(0)
T
0

This worked for me, Kotlin version:

In your Dialog:

override fun onStart() {
    super.onStart()
    UserInteractionInterceptor.wrapWindowCallback(dialog.window, activity)
}

and the UserInteractionInterceptor Class:

object UserInteractionInterceptor {
fun wrapWindowCallback(window: Window, activity: FragmentActivity?) {
    val originalCallback: Window.Callback = window.callback

    window.callback = object : Window.Callback {
        override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
            return originalCallback.dispatchKeyEvent(event)
        }

        override fun dispatchKeyShortcutEvent(event: KeyEvent?): Boolean {
            return originalCallback.dispatchKeyShortcutEvent(event)
        }

        override fun dispatchTouchEvent(event: MotionEvent): Boolean {
            if (event.action == MotionEvent.ACTION_DOWN) {
                activity?.onUserInteraction()
            }
            return originalCallback.dispatchTouchEvent(event)
        }

        override fun dispatchTrackballEvent(event: MotionEvent?): Boolean {
            return originalCallback.dispatchTrackballEvent(event)
        }

        override fun dispatchGenericMotionEvent(event: MotionEvent?): Boolean {
            return originalCallback.dispatchGenericMotionEvent(event)
        }

        override fun dispatchPopulateAccessibilityEvent(event: AccessibilityEvent?): Boolean {
            return originalCallback.dispatchPopulateAccessibilityEvent(event)
        }

        @Nullable
        override fun onCreatePanelView(featureId: Int): View? {
            return originalCallback.onCreatePanelView(featureId)
        }

        override fun onCreatePanelMenu(featureId: Int, p1: Menu): Boolean {
            return originalCallback.onCreatePanelMenu(featureId, p1)
        }

        override fun onPreparePanel(featureId: Int, view: View?, p2: Menu): Boolean {
            return originalCallback.onPreparePanel(featureId, view, p2)
        }

        override fun onMenuOpened(featureId: Int, p1: Menu): Boolean {
            return originalCallback.onMenuOpened(featureId, p1)
        }

        override fun onMenuItemSelected(featureId: Int, p1: MenuItem): Boolean {
            return originalCallback.onMenuItemSelected(featureId, p1)
        }

        override fun onWindowAttributesChanged(attrs: WindowManager.LayoutParams?) {
            originalCallback.onWindowAttributesChanged(attrs)
        }

        override fun onContentChanged() {
            originalCallback.onContentChanged()
        }

        override fun onWindowFocusChanged(hasFocus: Boolean) {
            originalCallback.onWindowFocusChanged(hasFocus)
        }

        override fun onAttachedToWindow() {
            originalCallback.onAttachedToWindow()
        }

        override fun onDetachedFromWindow() {
            originalCallback.onDetachedFromWindow()
        }

        override fun onPanelClosed(featureId: Int, p1: Menu) {
            originalCallback.onPanelClosed(featureId, p1)
        }

        override fun onSearchRequested(): Boolean {
            return originalCallback.onSearchRequested()
        }

        @TargetApi(Build.VERSION_CODES.M)
        override fun onSearchRequested(searchEvent: SearchEvent?): Boolean {
            return originalCallback.onSearchRequested(searchEvent)
        }

        @Nullable
        override fun onWindowStartingActionMode(callback: ActionMode.Callback?): ActionMode? {
            return originalCallback.onWindowStartingActionMode(callback)
        }

        @TargetApi(Build.VERSION_CODES.M)
        @Nullable
        override fun onWindowStartingActionMode(callback: ActionMode.Callback?, type: Int): ActionMode? {
            return originalCallback.onWindowStartingActionMode(callback, type)
        }

        override fun onActionModeStarted(mode: ActionMode?) {
            originalCallback.onActionModeStarted(mode)
        }

        override fun onActionModeFinished(mode: ActionMode?) {
            originalCallback.onActionModeFinished(mode)
        }
    }
}

}

Tennyson answered 29/11, 2022 at 17:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.