1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.appcompat.app; 18 19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; 20 21 import android.content.Context; 22 import android.os.Bundle; 23 import android.util.TypedValue; 24 import android.view.KeyEvent; 25 import android.view.View; 26 import android.view.ViewGroup; 27 28 import androidx.activity.ComponentDialog; 29 import androidx.activity.ViewTreeOnBackPressedDispatcherOwner; 30 import androidx.annotation.IdRes; 31 import androidx.annotation.LayoutRes; 32 import androidx.annotation.RestrictTo; 33 import androidx.appcompat.R; 34 import androidx.appcompat.view.ActionMode; 35 import androidx.core.view.KeyEventDispatcher; 36 import androidx.lifecycle.ViewTreeLifecycleOwner; 37 import androidx.savedstate.ViewTreeSavedStateRegistryOwner; 38 39 import org.jspecify.annotations.NonNull; 40 import org.jspecify.annotations.Nullable; 41 42 /** 43 * Base class for AppCompat themed {@link android.app.Dialog}s. 44 */ 45 @SuppressWarnings("unused") 46 public class AppCompatDialog extends ComponentDialog implements AppCompatCallback { 47 48 private AppCompatDelegate mDelegate; 49 50 // Until KeyEventDispatcher is un-hidden, it can't be implemented directly, 51 private final KeyEventDispatcher.Component mKeyDispatcher = 52 AppCompatDialog.this::superDispatchKeyEvent; 53 AppCompatDialog(@onNull Context context)54 public AppCompatDialog(@NonNull Context context) { 55 this(context, 0); 56 } 57 AppCompatDialog(@onNull Context context, int theme)58 public AppCompatDialog(@NonNull Context context, int theme) { 59 super(context, getThemeResId(context, theme)); 60 61 final AppCompatDelegate delegate = getDelegate(); 62 // Make sure we provide the delegate with the current theme res id 63 delegate.setTheme(getThemeResId(context, theme)); 64 65 // This is a bit weird, but Dialog's are typically created and setup before being shown, 66 // which means that we can't rely on onCreate() being called before a content view is set. 67 // To workaround this, we call onCreate(null) in the ctor, and then again as usual in 68 // onCreate(). 69 delegate.onCreate(null); 70 } 71 AppCompatDialog(@onNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener)72 protected AppCompatDialog(@NonNull Context context, boolean cancelable, 73 @Nullable OnCancelListener cancelListener) { 74 super(context); 75 setCancelable(cancelable); 76 setOnCancelListener(cancelListener); 77 } 78 79 @Override onCreate(Bundle savedInstanceState)80 protected void onCreate(Bundle savedInstanceState) { 81 getDelegate().installViewFactory(); 82 super.onCreate(savedInstanceState); 83 getDelegate().onCreate(savedInstanceState); 84 } 85 86 /** 87 * Support library version of {@link android.app.Dialog#getActionBar}. 88 * 89 * <p>Retrieve a reference to this dialog's ActionBar. 90 * 91 * @return The Dialog's ActionBar, or null if it does not have one. 92 */ getSupportActionBar()93 public ActionBar getSupportActionBar() { 94 return getDelegate().getSupportActionBar(); 95 } 96 97 @Override setContentView(@ayoutRes int layoutResID)98 public void setContentView(@LayoutRes int layoutResID) { 99 initViewTreeOwners(); 100 getDelegate().setContentView(layoutResID); 101 } 102 103 @Override setContentView(@onNull View view)104 public void setContentView(@NonNull View view) { 105 initViewTreeOwners(); 106 getDelegate().setContentView(view); 107 } 108 109 @Override setContentView(@onNull View view, ViewGroup.LayoutParams params)110 public void setContentView(@NonNull View view, ViewGroup.LayoutParams params) { 111 initViewTreeOwners(); 112 getDelegate().setContentView(view, params); 113 } 114 initViewTreeOwners()115 private void initViewTreeOwners() { 116 // Set the view tree owners before setting the content view so that the inflation process 117 // and attach listeners will see them already present 118 ViewTreeLifecycleOwner.set(getWindow().getDecorView(), this); 119 ViewTreeSavedStateRegistryOwner.set(getWindow().getDecorView(), this); 120 ViewTreeOnBackPressedDispatcherOwner.set(getWindow().getDecorView(), this); 121 } 122 123 @SuppressWarnings("TypeParameterUnusedInFormals") 124 @Override findViewById(@dRes int id)125 public <T extends View> @Nullable T findViewById(@IdRes int id) { 126 return getDelegate().findViewById(id); 127 } 128 129 @Override setTitle(CharSequence title)130 public void setTitle(CharSequence title) { 131 super.setTitle(title); 132 getDelegate().setTitle(title); 133 } 134 135 @Override setTitle(int titleId)136 public void setTitle(int titleId) { 137 super.setTitle(titleId); 138 getDelegate().setTitle(getContext().getString(titleId)); 139 } 140 141 @Override addContentView(@onNull View view, ViewGroup.LayoutParams params)142 public void addContentView(@NonNull View view, ViewGroup.LayoutParams params) { 143 initViewTreeOwners(); 144 getDelegate().addContentView(view, params); 145 } 146 147 @Override onStop()148 protected void onStop() { 149 super.onStop(); 150 getDelegate().onStop(); 151 } 152 153 @Override dismiss()154 public void dismiss() { 155 super.dismiss(); 156 // There is no onDestroy in Dialog, so we simulate it from dismiss() 157 getDelegate().onDestroy(); 158 } 159 160 /** 161 * Enable extended support library window features. 162 * <p> 163 * This is a convenience for calling 164 * {@link android.view.Window#requestFeature getWindow().requestFeature()}. 165 * </p> 166 * 167 * @param featureId The desired feature as defined in {@link android.view.Window} or 168 * {@link androidx.core.view.WindowCompat}. 169 * @return Returns true if the requested feature is supported and now enabled. 170 * 171 * @see android.app.Dialog#requestWindowFeature 172 * @see android.view.Window#requestFeature 173 */ supportRequestWindowFeature(int featureId)174 public boolean supportRequestWindowFeature(int featureId) { 175 return getDelegate().requestWindowFeature(featureId); 176 } 177 178 /** 179 */ 180 @Override 181 @RestrictTo(LIBRARY_GROUP_PREFIX) invalidateOptionsMenu()182 public void invalidateOptionsMenu() { 183 getDelegate().invalidateOptionsMenu(); 184 } 185 186 /** 187 * @return The {@link AppCompatDelegate} being used by this Dialog. 188 */ getDelegate()189 public @NonNull AppCompatDelegate getDelegate() { 190 if (mDelegate == null) { 191 mDelegate = AppCompatDelegate.create(this, this); 192 } 193 return mDelegate; 194 } 195 getThemeResId(Context context, int themeId)196 private static int getThemeResId(Context context, int themeId) { 197 if (themeId == 0) { 198 // If the provided theme is 0, then retrieve the dialogTheme from our theme 199 TypedValue outValue = new TypedValue(); 200 context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true); 201 themeId = outValue.resourceId; 202 } 203 return themeId; 204 } 205 206 @Override onSupportActionModeStarted(ActionMode mode)207 public void onSupportActionModeStarted(ActionMode mode) { 208 } 209 210 @Override onSupportActionModeFinished(ActionMode mode)211 public void onSupportActionModeFinished(ActionMode mode) { 212 } 213 214 @Override onWindowStartingSupportActionMode(ActionMode.Callback callback)215 public @Nullable ActionMode onWindowStartingSupportActionMode(ActionMode.Callback callback) { 216 return null; 217 } 218 219 @SuppressWarnings("WeakerAccess") /* synthetic access */ superDispatchKeyEvent(KeyEvent event)220 boolean superDispatchKeyEvent(KeyEvent event) { 221 return super.dispatchKeyEvent(event); 222 } 223 224 @Override dispatchKeyEvent(KeyEvent event)225 public boolean dispatchKeyEvent(KeyEvent event) { 226 View decor = getWindow().getDecorView(); 227 return KeyEventDispatcher.dispatchKeyEvent(mKeyDispatcher, decor, this, event); 228 } 229 } 230