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