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
17package com.android.messaging.ui;
18
19import android.graphics.drawable.ColorDrawable;
20import android.os.Bundle;
21import android.support.v7.app.ActionBar;
22import android.support.v7.app.ActionBarActivity;
23import android.view.ActionMode;
24import android.view.Menu;
25import android.view.MenuInflater;
26import android.view.MenuItem;
27import android.view.View;
28
29import com.android.messaging.R;
30import com.android.messaging.util.BugleActivityUtil;
31import com.android.messaging.util.ImeUtil;
32import com.android.messaging.util.LogUtil;
33import com.android.messaging.util.UiUtils;
34
35import java.util.HashSet;
36import java.util.Set;
37
38/**
39 * Base class for app activities that use an action bar. Responsible for logging/telemetry/other
40 * needs that will be common for all activities.  We can break out the common code if/when we need
41 * a version that doesn't use an actionbar.
42 */
43public class BugleActionBarActivity extends ActionBarActivity implements ImeUtil.ImeStateHost {
44    // Tracks the list of observers opting in for IME state change.
45    private final Set<ImeUtil.ImeStateObserver> mImeStateObservers = new HashSet<>();
46
47    // Tracks the soft keyboard display state
48    private boolean mImeOpen;
49
50    // The ActionMode that represents the modal contextual action bar, using our own implementation
51    // rather than the built in contextual action bar to reduce jank
52    private CustomActionMode mActionMode;
53
54    // The menu for the action bar
55    private Menu mActionBarMenu;
56
57    // Used to determine if a onDisplayHeightChanged was due to the IME opening or rotation of the
58    // device
59    private int mLastScreenHeight;
60
61    @Override
62    protected void onCreate(final Bundle savedInstanceState) {
63        super.onCreate(savedInstanceState);
64        if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
65            return;
66        }
67
68        mLastScreenHeight = getResources().getDisplayMetrics().heightPixels;
69        if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
70            LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onCreate");
71        }
72    }
73
74    @Override
75    protected void onStart() {
76        super.onStart();
77        if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
78            LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onStart");
79        }
80    }
81
82    @Override
83    protected void onRestart() {
84        super.onStop();
85        if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
86            LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onRestart");
87        }
88    }
89
90    @Override
91    protected void onResume() {
92        super.onResume();
93        if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
94            LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume");
95        }
96        BugleActivityUtil.onActivityResume(this, BugleActionBarActivity.this);
97    }
98
99    @Override
100    protected void onPause() {
101        super.onPause();
102        if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
103            LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause");
104        }
105    }
106
107    @Override
108    protected void onStop() {
109        super.onStop();
110        if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
111            LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onStop");
112        }
113    }
114
115    private boolean mDestroyed;
116
117    @Override
118    protected void onDestroy() {
119        super.onDestroy();
120        mDestroyed = true;
121    }
122
123    public boolean getIsDestroyed() {
124        return mDestroyed;
125    }
126
127    @Override
128    public void onDisplayHeightChanged(final int heightSpecification) {
129        int screenHeight = getResources().getDisplayMetrics().heightPixels;
130
131        if (screenHeight != mLastScreenHeight) {
132            // Appears to be an orientation change, don't fire ime updates
133            mLastScreenHeight = screenHeight;
134            LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onDisplayHeightChanged " +
135                    " screenHeight: " + screenHeight + " lastScreenHeight: " + mLastScreenHeight +
136                    " Skipped, appears to be orientation change.");
137            return;
138        }
139        final ActionBar actionBar = getSupportActionBar();
140        if (actionBar != null && actionBar.isShowing()) {
141            screenHeight -= actionBar.getHeight();
142        }
143        final int height = View.MeasureSpec.getSize(heightSpecification);
144
145        final boolean imeWasOpen = mImeOpen;
146        mImeOpen = screenHeight - height > 100;
147
148        if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
149            LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onDisplayHeightChanged " +
150                    "imeWasOpen: " + imeWasOpen + " mImeOpen: " + mImeOpen + " screenHeight: " +
151                    screenHeight + " height: " + height);
152        }
153
154        if (imeWasOpen != mImeOpen) {
155            for (final ImeUtil.ImeStateObserver observer : mImeStateObservers) {
156                observer.onImeStateChanged(mImeOpen);
157            }
158        }
159    }
160
161    @Override
162    public void registerImeStateObserver(final ImeUtil.ImeStateObserver observer) {
163        mImeStateObservers.add(observer);
164    }
165
166    @Override
167    public void unregisterImeStateObserver(final ImeUtil.ImeStateObserver observer) {
168        mImeStateObservers.remove(observer);
169    }
170
171    @Override
172    public boolean isImeOpen() {
173        return mImeOpen;
174    }
175
176    @Override
177    public boolean onCreateOptionsMenu(final Menu menu) {
178        mActionBarMenu = menu;
179        if (mActionMode != null &&
180                mActionMode.getCallback().onCreateActionMode(mActionMode, menu)) {
181            return true;
182        }
183        return false;
184    }
185
186    @Override
187    public boolean onPrepareOptionsMenu(final Menu menu) {
188        mActionBarMenu = menu;
189        if (mActionMode != null &&
190                mActionMode.getCallback().onPrepareActionMode(mActionMode, menu)) {
191            return true;
192        }
193        return super.onPrepareOptionsMenu(menu);
194    }
195
196    @Override
197    public boolean onOptionsItemSelected(final MenuItem menuItem) {
198        if (mActionMode != null &&
199                mActionMode.getCallback().onActionItemClicked(mActionMode, menuItem)) {
200            return true;
201        }
202
203        switch (menuItem.getItemId()) {
204            case android.R.id.home:
205                if (mActionMode != null) {
206                    dismissActionMode();
207                    return true;
208                }
209        }
210        return super.onOptionsItemSelected(menuItem);
211    }
212
213    @Override
214    public ActionMode startActionMode(final ActionMode.Callback callback) {
215        mActionMode = new CustomActionMode(callback);
216        supportInvalidateOptionsMenu();
217        invalidateActionBar();
218        return mActionMode;
219    }
220
221    public void dismissActionMode() {
222        if (mActionMode != null) {
223            mActionMode.finish();
224            mActionMode = null;
225            invalidateActionBar();
226        }
227    }
228
229    public ActionMode getActionMode() {
230        return mActionMode;
231    }
232
233    protected ActionMode.Callback getActionModeCallback() {
234        if (mActionMode == null) {
235            return null;
236        }
237
238        return mActionMode.getCallback();
239    }
240
241    /**
242     * Receives and handles action bar invalidation request from sub-components of this activity.
243     *
244     * <p>Normally actions have sole control over the action bar, but in order to support seamless
245     * transitions for components such as the full screen media picker, we have to let it take over
246     * the action bar and then restore its state afterwards</p>
247     *
248     * <p>If a fragment does anything that may change the action bar, it should call this method
249     * and then it is this method's responsibility to figure out which component "controls" the
250     * action bar and delegate the updating of the action bar to that component</p>
251     */
252    public final void invalidateActionBar() {
253        if (mActionMode != null) {
254            mActionMode.updateActionBar(getSupportActionBar());
255        } else {
256            updateActionBar(getSupportActionBar());
257        }
258    }
259
260    protected void updateActionBar(final ActionBar actionBar) {
261        actionBar.setHomeAsUpIndicator(null);
262    }
263
264    /**
265     * Custom ActionMode implementation which allows us to just replace the contents of the main
266     * action bar rather than overlay over it
267     */
268    private class CustomActionMode extends ActionMode {
269        private CharSequence mTitle;
270        private CharSequence mSubtitle;
271        private View mCustomView;
272        private final Callback mCallback;
273
274        public CustomActionMode(final Callback callback) {
275            mCallback = callback;
276        }
277
278        @Override
279        public void setTitle(final CharSequence title) {
280            mTitle = title;
281        }
282
283        @Override
284        public void setTitle(final int resId) {
285            mTitle = getResources().getString(resId);
286        }
287
288        @Override
289        public void setSubtitle(final CharSequence subtitle) {
290            mSubtitle = subtitle;
291        }
292
293        @Override
294        public void setSubtitle(final int resId) {
295            mSubtitle = getResources().getString(resId);
296        }
297
298        @Override
299        public void setCustomView(final View view) {
300            mCustomView = view;
301        }
302
303        @Override
304        public void invalidate() {
305            invalidateActionBar();
306        }
307
308        @Override
309        public void finish() {
310            mActionMode = null;
311            mCallback.onDestroyActionMode(this);
312            supportInvalidateOptionsMenu();
313            invalidateActionBar();
314        }
315
316        @Override
317        public Menu getMenu() {
318            return mActionBarMenu;
319        }
320
321        @Override
322        public CharSequence getTitle() {
323            return mTitle;
324        }
325
326        @Override
327        public CharSequence getSubtitle() {
328            return mSubtitle;
329        }
330
331        @Override
332        public View getCustomView() {
333            return mCustomView;
334        }
335
336        @Override
337        public MenuInflater getMenuInflater() {
338            return BugleActionBarActivity.this.getMenuInflater();
339        }
340
341        public Callback getCallback() {
342            return mCallback;
343        }
344
345        public void updateActionBar(final ActionBar actionBar) {
346            actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP);
347            actionBar.setDisplayShowTitleEnabled(false);
348            actionBar.setDisplayShowCustomEnabled(false);
349            mActionMode.getCallback().onPrepareActionMode(mActionMode, mActionBarMenu);
350            actionBar.setBackgroundDrawable(new ColorDrawable(
351                    getResources().getColor(R.color.contextual_action_bar_background_color)));
352            actionBar.setHomeAsUpIndicator(R.drawable.ic_cancel_small_dark);
353            actionBar.show();
354        }
355    }
356}
357