/* * Copyright (C) 2006 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 android.app; import android.annotation.CallSuper; import android.annotation.DrawableRes; import android.annotation.IdRes; import android.annotation.LayoutRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.StyleRes; import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.DialogInterface; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.content.res.ResourceId; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import android.util.TypedValue; import android.view.ActionMode; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.SearchEvent; import android.view.View; import android.view.View.OnCreateContextMenuListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import com.android.internal.R; import com.android.internal.app.WindowDecorActionBar; import com.android.internal.policy.PhoneWindow; import java.lang.ref.WeakReference; /** * Base class for Dialogs. * *
Note: Activities provide a facility to manage the creation, saving and * restoring of dialogs. See {@link Activity#onCreateDialog(int)}, * {@link Activity#onPrepareDialog(int, Dialog)}, * {@link Activity#showDialog(int)}, and {@link Activity#dismissDialog(int)}. If * these methods are used, {@link #getOwnerActivity()} will return the Activity * that managed this dialog. * *
Often you will want to have a Dialog display on top of the current * input method, because there is no reason for it to accept text. You can * do this by setting the {@link WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM * WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM} window flag (assuming * your Dialog takes input focus, as it the default) with the following code: * *
* getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, * WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);* *
For more information about creating dialogs, read the * Dialogs developer guide.
** The supplied {@code context} is used to obtain the window manager and * base theme used to present the dialog. * * @param context the context in which the dialog should run * @see android.R.styleable#Theme_dialogTheme */ public Dialog(@NonNull Context context) { this(context, 0, true); } /** * Creates a dialog window that uses a custom dialog style. *
* The supplied {@code context} is used to obtain the window manager and * base theme used to present the dialog. *
* The supplied {@code theme} is applied on top of the context's theme. See * * Style and Theme Resources for more information about defining and * using styles. * * @param context the context in which the dialog should run * @param themeResId a style resource describing the theme to use for the * window, or {@code 0} to use the default dialog theme */ public Dialog(@NonNull Context context, @StyleRes int themeResId) { this(context, themeResId, true); } Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { if (createContextThemeWrapper) { if (themeResId == ResourceId.ID_NULL) { final TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true); themeResId = outValue.resourceId; } mContext = new ContextThemeWrapper(context, themeResId); } else { mContext = context; } mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); final Window w = new PhoneWindow(mContext); mWindow = w; w.setCallback(this); w.setOnWindowDismissedCallback(this); w.setOnWindowSwipeDismissedCallback(() -> { if (mCancelable) { cancel(); } }); w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); } /** * @deprecated * @hide */ @Deprecated protected Dialog(@NonNull Context context, boolean cancelable, @Nullable Message cancelCallback) { this(context); mCancelable = cancelable; updateWindowForCancelable(); mCancelMessage = cancelCallback; } protected Dialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) { this(context); mCancelable = cancelable; updateWindowForCancelable(); setOnCancelListener(cancelListener); } /** * Retrieve the Context this Dialog is running in. * * @return Context The Context used by the Dialog. */ public final @NonNull Context getContext() { return mContext; } /** * Retrieve the {@link ActionBar} attached to this dialog, if present. * * @return The ActionBar attached to the dialog or null if no ActionBar is present. */ public @Nullable ActionBar getActionBar() { return mActionBar; } /** * Sets the Activity that owns this dialog. An example use: This Dialog will * use the suggested volume control stream of the Activity. * * @param activity The Activity that owns this dialog. */ public final void setOwnerActivity(@NonNull Activity activity) { mOwnerActivity = activity; getWindow().setVolumeControlStream(mOwnerActivity.getVolumeControlStream()); } /** * Returns the Activity that owns this Dialog. For example, if * {@link Activity#showDialog(int)} is used to show this Dialog, that * Activity will be the owner (by default). Depending on how this dialog was * created, this may return null. * * @return The Activity that owns this Dialog. */ public final @Nullable Activity getOwnerActivity() { return mOwnerActivity; } /** * @return Whether the dialog is currently showing. */ public boolean isShowing() { return mShowing; } /** * Forces immediate creation of the dialog. *
* Note that you should not override this method to perform dialog creation. * Rather, override {@link #onCreate(Bundle)}. */ public void create() { if (!mCreated) { dispatchOnCreate(null); } } /** * Start the dialog and display it on screen. The window is placed in the * application layer and opaque. Note that you should not override this * method to do initialization when the dialog is shown, instead implement * that in {@link #onStart}. */ public void show() { if (mShowing) { if (mDecor != null) { if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR); } mDecor.setVisibility(View.VISIBLE); } return; } mCanceled = false; if (!mCreated) { dispatchOnCreate(null); } else { // Fill the DecorView in on any configuration changes that // may have occured while it was removed from the WindowManager. final Configuration config = mContext.getResources().getConfiguration(); mWindow.getDecorView().dispatchConfigurationChanged(config); } onStart(); mDecor = mWindow.getDecorView(); if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { final ApplicationInfo info = mContext.getApplicationInfo(); mWindow.setDefaultIcon(info.icon); mWindow.setDefaultLogo(info.logo); mActionBar = new WindowDecorActionBar(this); } WindowManager.LayoutParams l = mWindow.getAttributes(); boolean restoreSoftInputMode = false; if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) { l.softInputMode |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; restoreSoftInputMode = true; } mWindowManager.addView(mDecor, l); if (restoreSoftInputMode) { l.softInputMode &= ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; } mShowing = true; sendShowMessage(); } /** * Hide the dialog, but do not dismiss it. */ public void hide() { if (mDecor != null) { mDecor.setVisibility(View.GONE); } } /** * Dismiss this dialog, removing it from the screen. This method can be * invoked safely from any thread. Note that you should not override this * method to do cleanup when the dialog is dismissed, instead implement * that in {@link #onStop}. */ @Override public void dismiss() { if (Looper.myLooper() == mHandler.getLooper()) { dismissDialog(); } else { mHandler.post(mDismissAction); } } void dismissDialog() { if (mDecor == null || !mShowing) { return; } if (mWindow.isDestroyed()) { Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!"); return; } try { mWindowManager.removeViewImmediate(mDecor); } finally { if (mActionMode != null) { mActionMode.finish(); } mDecor = null; mWindow.closeAllPanels(); onStop(); mShowing = false; sendDismissMessage(); } } private void sendDismissMessage() { if (mDismissMessage != null) { // Obtain a new message so this dialog can be re-used Message.obtain(mDismissMessage).sendToTarget(); } } private void sendShowMessage() { if (mShowMessage != null) { // Obtain a new message so this dialog can be re-used Message.obtain(mShowMessage).sendToTarget(); } } // internal method to make sure mCreated is set properly without requiring // users to call through to super in onCreate void dispatchOnCreate(Bundle savedInstanceState) { if (!mCreated) { onCreate(savedInstanceState); mCreated = true; } } /** * Similar to {@link Activity#onCreate}, you should initialize your dialog * in this method, including calling {@link #setContentView}. * @param savedInstanceState If this dialog is being reinitialized after a * the hosting activity was previously shut down, holds the result from * the most recent call to {@link #onSaveInstanceState}, or null if this * is the first time. */ protected void onCreate(Bundle savedInstanceState) { } /** * Called when the dialog is starting. */ protected void onStart() { if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true); } /** * Called to tell you that you're stopping. */ protected void onStop() { if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); } private static final String DIALOG_SHOWING_TAG = "android:dialogShowing"; private static final String DIALOG_HIERARCHY_TAG = "android:dialogHierarchy"; /** * Saves the state of the dialog into a bundle. * * The default implementation saves the state of its view hierarchy, so you'll * likely want to call through to super if you override this to save additional * state. * @return A bundle with the state of the dialog. */ public @NonNull Bundle onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putBoolean(DIALOG_SHOWING_TAG, mShowing); if (mCreated) { bundle.putBundle(DIALOG_HIERARCHY_TAG, mWindow.saveHierarchyState()); } return bundle; } /** * Restore the state of the dialog from a previously saved bundle. * * The default implementation restores the state of the dialog's view * hierarchy that was saved in the default implementation of {@link #onSaveInstanceState()}, * so be sure to call through to super when overriding unless you want to * do all restoring of state yourself. * @param savedInstanceState The state of the dialog previously saved by * {@link #onSaveInstanceState()}. */ public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { final Bundle dialogHierarchyState = savedInstanceState.getBundle(DIALOG_HIERARCHY_TAG); if (dialogHierarchyState == null) { // dialog has never been shown, or onCreated, nothing to restore. return; } dispatchOnCreate(savedInstanceState); mWindow.restoreHierarchyState(dialogHierarchyState); if (savedInstanceState.getBoolean(DIALOG_SHOWING_TAG)) { show(); } } /** * Retrieve the current Window for the activity. This can be used to * directly access parts of the Window API that are not available * through Activity/Screen. * * @return Window The current window, or null if the activity is not * visual. */ public @Nullable Window getWindow() { return mWindow; } /** * Call {@link android.view.Window#getCurrentFocus} on the * Window if this Activity to return the currently focused view. * * @return View The current View with focus or null. * * @see #getWindow * @see android.view.Window#getCurrentFocus */ public @Nullable View getCurrentFocus() { return mWindow != null ? mWindow.getCurrentFocus() : null; } /** * Finds the first descendant view with the given ID or {@code null} if the * ID is invalid (< 0), there is no matching view in the hierarchy, or the * dialog has not yet been fully created (for example, via {@link #show()} * or {@link #create()}). *
* Note: In most cases -- depending on compiler support --
* the resulting view is automatically cast to the target class type. If
* the target class type is unconstrained, an explicit cast may be
* necessary.
*
* @param id the ID to search for
* @return a view with given ID if found, or {@code null} otherwise
* @see View#findViewById(int)
* @see Dialog#requireViewById(int)
*/
@Nullable
public
* Note: In most cases -- depending on compiler support --
* the resulting view is automatically cast to the target class type. If
* the target class type is unconstrained, an explicit cast may be
* necessary.
*
* @param id the ID to search for
* @return a view with given ID
* @see View#requireViewById(int)
* @see Dialog#findViewById(int)
*/
@NonNull
public final
* If the focused view didn't want this event, this method is called.
*
* Default implementation consumes {@link KeyEvent#KEYCODE_BACK KEYCODE_BACK}
* and, as of {@link android.os.Build.VERSION_CODES#P P}, {@link KeyEvent#KEYCODE_ESCAPE
* KEYCODE_ESCAPE} to later handle them in {@link #onKeyUp}.
*
* @see #onKeyUp
* @see android.view.KeyEvent
*/
@Override
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
event.startTracking();
return true;
}
return false;
}
/**
* Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
* KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
* the event).
*/
@Override
public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
return false;
}
/**
* A key was released.
*
* Default implementation consumes {@link KeyEvent#KEYCODE_BACK KEYCODE_BACK}
* and, as of {@link android.os.Build.VERSION_CODES#P P}, {@link KeyEvent#KEYCODE_ESCAPE
* KEYCODE_ESCAPE} to close the dialog.
*
* @see #onKeyDown
* @see android.view.KeyEvent
*/
@Override
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE)
&& event.isTracking()
&& !event.isCanceled()) {
onBackPressed();
return true;
}
return false;
}
/**
* Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
* KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
* the event).
*/
@Override
public boolean onKeyMultiple(int keyCode, int repeatCount, @NonNull KeyEvent event) {
return false;
}
/**
* Called when the dialog has detected the user's press of the back
* key. The default implementation simply cancels the dialog (only if
* it is cancelable), but you can override this to do whatever you want.
*/
public void onBackPressed() {
if (mCancelable) {
cancel();
}
}
/**
* Called when a key shortcut event is not handled by any of the views in the Dialog.
* Override this method to implement global key shortcuts for the Dialog.
* Key shortcuts can also be implemented by setting the
* {@link MenuItem#setShortcut(char, char) shortcut} property of menu items.
*
* @param keyCode The value in event.getKeyCode().
* @param event Description of the key event.
* @return True if the key shortcut was handled.
*/
public boolean onKeyShortcut(int keyCode, @NonNull KeyEvent event) {
return false;
}
/**
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen outside
* of your window bounds, where there is no view to receive it.
*
* @param event The touch screen event being processed.
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation will cancel the dialog when a touch
* happens outside of the window bounds.
*/
public boolean onTouchEvent(@NonNull MotionEvent event) {
if (mCancelable && mShowing && mWindow.shouldCloseOnTouch(mContext, event)) {
cancel();
return true;
}
return false;
}
/**
* Called when the trackball was moved and not handled by any of the
* views inside of the activity. So, for example, if the trackball moves
* while focus is on a button, you will receive a call here because
* buttons do not normally do anything with trackball events. The call
* here happens before trackball movements are converted to
* DPAD key events, which then get sent back to the view hierarchy, and
* will be processed at the point for things like focus navigation.
*
* @param event The trackball event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public boolean onTrackballEvent(@NonNull MotionEvent event) {
return false;
}
/**
* Called when a generic motion event was not handled by any of the
* views inside of the dialog.
*
* Generic motion events describe joystick movements, mouse hovers, track pad
* touches, scroll wheel movements and other input events. The
* {@link MotionEvent#getSource() source} of the motion event specifies
* the class of input that was received. Implementations of this method
* must examine the bits in the source before processing the event.
* The following code example shows how this is done.
*
* Generic motion events with source class
* {@link android.view.InputDevice#SOURCE_CLASS_POINTER}
* are delivered to the view under the pointer. All other generic motion events are
* delivered to the focused view.
*
* See {@link View#onGenericMotionEvent(MotionEvent)} for an example of how to
* handle this event.
* This will only be invoked when the dialog is canceled.
* Cancel events alone will not capture all ways that
* the dialog might be dismissed. If the creator needs
* to know when a dialog is dismissed in general, use
* {@link #setOnDismissListener}.