AppCompatDelegateImplBase.java revision 88c0ac5649d6b82c882e2de4bc4f043b107a4428
1/*
2 * Copyright (C) 2013 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
17package android.support.v7.app;
18
19import android.app.Activity;
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.graphics.drawable.Drawable;
23import android.os.Bundle;
24import android.support.v7.appcompat.R;
25import android.support.v7.internal.view.SupportMenuInflater;
26import android.support.v7.internal.view.WindowCallbackWrapper;
27import android.support.v7.internal.view.menu.MenuBuilder;
28import android.support.v7.internal.widget.TintTypedArray;
29import android.support.v7.view.ActionMode;
30import android.view.KeyEvent;
31import android.view.Menu;
32import android.view.MenuInflater;
33import android.view.View;
34import android.view.Window;
35
36abstract class AppCompatDelegateImplBase extends AppCompatDelegate {
37
38    final Context mContext;
39    final Window mWindow;
40    final Window.Callback mOriginalWindowCallback;
41    final AppCompatCallback mAppCompatCallback;
42
43    private ActionBar mActionBar;
44    private MenuInflater mMenuInflater;
45
46    // true if this activity has an action bar.
47    boolean mHasActionBar;
48    // true if this activity's action bar overlays other activity content.
49    boolean mOverlayActionBar;
50    // true if this any action modes should overlay the activity content
51    boolean mOverlayActionMode;
52    // true if this activity is floating (e.g. Dialog)
53    boolean mIsFloating;
54    // true if this activity has no title
55    boolean mWindowNoTitle;
56
57    private CharSequence mTitle;
58
59    private boolean mIsDestroyed;
60
61    AppCompatDelegateImplBase(Context context, Window window, AppCompatCallback callback) {
62        mContext = context;
63        mWindow = window;
64        mAppCompatCallback = callback;
65
66        mOriginalWindowCallback = mWindow.getCallback();
67        if (mOriginalWindowCallback instanceof AppCompatWindowCallbackBase) {
68            throw new IllegalStateException(
69                    "AppCompat has already installed itself into the Window");
70        }
71        // Now install the new callback
72        mWindow.setCallback(wrapWindowCallback(mOriginalWindowCallback));
73    }
74
75    abstract ActionBar initWindowDecorActionBar();
76
77    Window.Callback wrapWindowCallback(Window.Callback callback) {
78        return new AppCompatWindowCallbackBase(callback);
79    }
80
81    @Override
82    public ActionBar getSupportActionBar() {
83        // The Action Bar should be lazily created as hasActionBar
84        // could change after onCreate
85        mActionBar = initWindowDecorActionBar();
86        return mActionBar;
87    }
88
89    final ActionBar peekSupportActionBar() {
90        return mActionBar;
91    }
92
93    final void setSupportActionBar(ActionBar actionBar) {
94        mActionBar = actionBar;
95    }
96
97    @Override
98    public MenuInflater getMenuInflater() {
99        if (mMenuInflater == null) {
100            mMenuInflater = new SupportMenuInflater(getActionBarThemedContext());
101        }
102        return mMenuInflater;
103    }
104
105    @Override
106    public void onCreate(Bundle savedInstanceState) {
107        TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
108
109        if (!a.hasValue(R.styleable.Theme_windowActionBar)) {
110            a.recycle();
111            throw new IllegalStateException(
112                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
113        }
114
115        if (a.getBoolean(R.styleable.Theme_windowNoTitle, false)) {
116            requestWindowFeature(Window.FEATURE_NO_TITLE);
117        } else if (a.getBoolean(R.styleable.Theme_windowActionBar, false)) {
118            // Don't allow an action bar if there is no title.
119            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
120        }
121        if (a.getBoolean(R.styleable.Theme_windowActionBarOverlay, false)) {
122            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
123        }
124        if (a.getBoolean(R.styleable.Theme_windowActionModeOverlay, false)) {
125            requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
126        }
127        mIsFloating = a.getBoolean(R.styleable.Theme_android_windowIsFloating, false);
128        a.recycle();
129    }
130
131    // Methods used to create and respond to options menu
132    abstract void onPanelClosed(int featureId, Menu menu);
133
134    abstract boolean onMenuOpened(int featureId, Menu menu);
135
136    abstract boolean dispatchKeyEvent(KeyEvent event);
137
138    abstract boolean onKeyShortcut(int keyCode, KeyEvent event);
139
140    @Override
141    public void setNightMode(@NightMode int mode) {
142        // no-op
143    }
144
145    @Override
146    public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() {
147        return new ActionBarDrawableToggleImpl();
148    }
149
150    final Context getActionBarThemedContext() {
151        Context context = null;
152
153        // If we have an action bar, let it return a themed context
154        ActionBar ab = getSupportActionBar();
155        if (ab != null) {
156            context = ab.getThemedContext();
157        }
158
159        if (context == null) {
160            context = mContext;
161        }
162        return context;
163    }
164
165    private class ActionBarDrawableToggleImpl implements ActionBarDrawerToggle.Delegate {
166        @Override
167        public Drawable getThemeUpIndicator() {
168            final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
169                    getActionBarThemedContext(), null, new int[]{ R.attr.homeAsUpIndicator });
170            final Drawable result = a.getDrawable(0);
171            a.recycle();
172            return result;
173        }
174
175        @Override
176        public Context getActionBarThemedContext() {
177            return AppCompatDelegateImplBase.this.getActionBarThemedContext();
178        }
179
180        @Override
181        public boolean isNavigationVisible() {
182            final ActionBar ab = getSupportActionBar();
183            return ab != null && (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0;
184        }
185
186        @Override
187        public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) {
188            ActionBar ab = getSupportActionBar();
189            if (ab != null) {
190                ab.setHomeAsUpIndicator(upDrawable);
191                ab.setHomeActionContentDescription(contentDescRes);
192            }
193        }
194
195        @Override
196        public void setActionBarDescription(int contentDescRes) {
197            ActionBar ab = getSupportActionBar();
198            if (ab != null) {
199                ab.setHomeActionContentDescription(contentDescRes);
200            }
201        }
202    }
203
204    abstract ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback);
205
206    @Override
207    public final void onDestroy() {
208        mIsDestroyed = true;
209    }
210
211    @Override
212    public void setHandleNativeActionModesEnabled(boolean enabled) {
213        // no-op pre-v14
214    }
215
216    @Override
217    public boolean isHandleNativeActionModesEnabled() {
218        // Always false pre-v14
219        return false;
220    }
221
222    @Override
223    public void applyDayNight() {
224        // no-op on v7
225    }
226
227    final boolean isDestroyed() {
228        return mIsDestroyed;
229    }
230
231    final Window.Callback getWindowCallback() {
232        return mWindow.getCallback();
233    }
234
235    @Override
236    public final void setTitle(CharSequence title) {
237        mTitle = title;
238        onTitleChanged(title);
239    }
240
241    abstract void onTitleChanged(CharSequence title);
242
243    final CharSequence getTitle() {
244        // If the original window callback is an Activity, we'll use it's title
245        if (mOriginalWindowCallback instanceof Activity) {
246            return ((Activity) mOriginalWindowCallback).getTitle();
247        }
248        // Else, we'll return the title we have recorded ourselves
249        return mTitle;
250    }
251
252    class AppCompatWindowCallbackBase extends WindowCallbackWrapper {
253        AppCompatWindowCallbackBase(Window.Callback callback) {
254            super(callback);
255        }
256
257        @Override
258        public boolean dispatchKeyEvent(KeyEvent event) {
259            return AppCompatDelegateImplBase.this.dispatchKeyEvent(event)
260                    || super.dispatchKeyEvent(event);
261        }
262
263        @Override
264        public boolean dispatchKeyShortcutEvent(KeyEvent event) {
265            return super.dispatchKeyShortcutEvent(event)
266                    || AppCompatDelegateImplBase.this.onKeyShortcut(event.getKeyCode(), event);
267        }
268
269        @Override
270        public boolean onCreatePanelMenu(int featureId, Menu menu) {
271            if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) {
272                // If this is an options menu but it's not an AppCompat menu, we eat the event
273                // and return false
274                return false;
275            }
276            return super.onCreatePanelMenu(featureId, menu);
277        }
278
279        @Override
280        public void onContentChanged() {
281            // We purposely do not propagate this call as this is called when we install
282            // our sub-decor rather than the user's content
283        }
284
285        @Override
286        public boolean onPreparePanel(int featureId, View view, Menu menu) {
287            final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null;
288
289            if (featureId == Window.FEATURE_OPTIONS_PANEL && mb == null) {
290                // If this is an options menu but it's not an AppCompat menu, we eat the event
291                // and return false
292                return false;
293            }
294
295            // On ICS and below devices, onPreparePanel calls menu.hasVisibleItems() to determine
296            // if a panel is prepared. This interferes with any initially invisible items, which
297            // are later made visible. We workaround it by making hasVisibleItems() always
298            // return true during the onPreparePanel call.
299            if (mb != null) {
300                mb.setOverrideVisibleItems(true);
301            }
302
303            final boolean handled = super.onPreparePanel(featureId, view, menu);
304
305            if (mb != null) {
306                mb.setOverrideVisibleItems(false);
307            }
308
309            return handled;
310        }
311
312        @Override
313        public boolean onMenuOpened(int featureId, Menu menu) {
314            return super.onMenuOpened(featureId, menu)
315                    || AppCompatDelegateImplBase.this.onMenuOpened(featureId, menu);
316        }
317
318        @Override
319        public void onPanelClosed(int featureId, Menu menu) {
320            super.onPanelClosed(featureId, menu);
321            AppCompatDelegateImplBase.this.onPanelClosed(featureId, menu);
322        }
323    }
324}
325