Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2010 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 com.android.camera.ui;
     18 
     19 import static com.android.camera.ui.GLRootView.dpToPixel;
     20 
     21 import java.util.ArrayList;
     22 
     23 import android.content.Context;
     24 import android.content.SharedPreferences;
     25 import android.content.SharedPreferences.Editor;
     26 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
     27 import android.graphics.Rect;
     28 import android.hardware.Camera.Parameters;
     29 import android.os.Handler;
     30 import android.os.Message;
     31 import android.util.DisplayMetrics;
     32 import android.view.MotionEvent;
     33 import android.view.View.MeasureSpec;
     34 import android.view.animation.AlphaAnimation;
     35 import android.view.animation.Animation;
     36 
     37 import com.android.camera.CameraSettings;
     38 import com.android.camera.ComboPreferences;
     39 import com.android.camera.IconListPreference;
     40 import com.android.camera.ListPreference;
     41 import com.android.camera.PreferenceGroup;
     42 import com.android.camera.R;
     43 
     44 // This is the UI for the on-screen settings. Since the rendering is run in the
     45 // GL thread. If any values will be changed in the main thread, it needs to
     46 // synchronize on the <code>GLRootView</code> instance.
     47 public class HeadUpDisplay extends GLView {
     48     private static final int INDICATOR_BAR_TIMEOUT = 5500;
     49     private static final int POPUP_WINDOW_TIMEOUT = 5000;
     50     private static final int INDICATOR_BAR_RIGHT_MARGIN = 10;
     51     private static final int POPUP_WINDOW_OVERLAP = 20;
     52     private static final int POPUP_TRIANGLE_OFFSET = 16;
     53 
     54     private static final int COLOR_ICONBAR_HIGHLIGHT = 0x9A2B2B2B;
     55 
     56     private static final float MAX_HEIGHT_RATIO = 0.85f;
     57     private static final float MAX_WIDTH_RATIO = 0.8f;
     58 
     59     private static final int DESELECT_INDICATOR = 0;
     60     private static final int DEACTIVATE_INDICATOR_BAR = 1;
     61 
     62     private static int sIndicatorBarRightMargin = -1;
     63     private static int sPopupWindowOverlap;
     64     private static int sPopupTriangleOffset;
     65 
     66     private static final String TAG = "HeadUpDisplay";
     67 
     68     protected IndicatorBar mIndicatorBar;
     69 
     70     private ComboPreferences mSharedPrefs;
     71     private PreferenceGroup mPreferenceGroup;
     72 
     73     private PopupWindow mPopupWindow;
     74 
     75     private GLView mAnchorView;
     76     private int mOrientation = 0;
     77     private boolean mEnabled = true;
     78 
     79     protected Listener mListener;
     80 
     81     private Handler mHandler = new Handler() {
     82         @Override
     83         public void handleMessage(Message msg) {
     84             GLRootView root = getGLRootView();
     85             if (root != null) {
     86                 synchronized (root) {
     87                     handleMessageLocked(msg);
     88                 }
     89             } else {
     90                 handleMessageLocked(msg);
     91             }
     92         }
     93 
     94         private void handleMessageLocked(Message msg) {
     95             switch(msg.what) {
     96                 case DESELECT_INDICATOR:
     97                     mIndicatorBar.setSelectedIndex(IndicatorBar.INDEX_NONE);
     98                     break;
     99                 case DEACTIVATE_INDICATOR_BAR:
    100                     if (mIndicatorBar != null) {
    101                         mIndicatorBar.setActivated(false);
    102                     }
    103                     break;
    104             }
    105         }
    106     };
    107 
    108     private final OnSharedPreferenceChangeListener mSharedPreferenceChangeListener =
    109             new OnSharedPreferenceChangeListener() {
    110         public void onSharedPreferenceChanged(
    111                 SharedPreferences sharedPreferences, String key) {
    112             if (mListener != null) {
    113                 mListener.onSharedPreferencesChanged();
    114             }
    115         }
    116     };
    117 
    118     public HeadUpDisplay(Context context) {
    119         initializeStaticVariables(context);
    120     }
    121 
    122     private static void initializeStaticVariables(Context context) {
    123         if (sIndicatorBarRightMargin >= 0) return;
    124 
    125         sIndicatorBarRightMargin = dpToPixel(context, INDICATOR_BAR_RIGHT_MARGIN);
    126         sPopupWindowOverlap = dpToPixel(context, POPUP_WINDOW_OVERLAP);
    127         sPopupTriangleOffset = dpToPixel(context, POPUP_TRIANGLE_OFFSET);
    128     }
    129 
    130     /**
    131      * The callback interface. All the callbacks will be called from the
    132      * GLThread.
    133      */
    134     static public interface Listener {
    135         public void onPopupWindowVisibilityChanged(int visibility);
    136         public void onRestorePreferencesClicked();
    137         public void onSharedPreferencesChanged();
    138     }
    139 
    140     public void overrideSettings(final String ... keyvalues) {
    141         GLRootView root = getGLRootView();
    142         if (root != null) {
    143             synchronized (root) {
    144                 overrideSettingsLocked(keyvalues);
    145             }
    146         } else {
    147             overrideSettingsLocked(keyvalues);
    148         }
    149     }
    150 
    151     public void overrideSettingsLocked(final String ... keyvalues) {
    152         if (keyvalues.length % 2 != 0) {
    153             throw new IllegalArgumentException();
    154         }
    155         for (int i = 0, n = keyvalues.length; i < n; i += 2) {
    156             mIndicatorBar.overrideSettings(keyvalues[i], keyvalues[i + 1]);
    157         }
    158     }
    159 
    160     @Override
    161     protected void onLayout(
    162             boolean changed, int left, int top, int right, int bottom) {
    163         int width = right - left;
    164         int height = bottom - top;
    165         mIndicatorBar.measure(
    166                 MeasureSpec.makeMeasureSpec(width / 3, MeasureSpec.AT_MOST),
    167                 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
    168         DisplayMetrics metrics = getGLRootView().getDisplayMetrics();
    169         int rightMargin = (int) (metrics.density * INDICATOR_BAR_RIGHT_MARGIN);
    170 
    171         mIndicatorBar.layout(
    172                 width - mIndicatorBar.getMeasuredWidth() - rightMargin, 0,
    173                 width - rightMargin, height);
    174 
    175         if(mPopupWindow != null
    176                 && mPopupWindow.getVisibility() == GLView.VISIBLE) {
    177             layoutPopupWindow(mAnchorView);
    178         }
    179     }
    180 
    181     public void initialize(Context context, PreferenceGroup preferenceGroup) {
    182         mPreferenceGroup = preferenceGroup;
    183         mSharedPrefs = ComboPreferences.get(context);
    184         mPopupWindow = null;
    185         clearComponents();
    186         initializeIndicatorBar(context, preferenceGroup);
    187         requestLayout();
    188     }
    189 
    190     private void layoutPopupWindow(GLView anchorView) {
    191 
    192         mAnchorView = anchorView;
    193         Rect rect = new Rect();
    194         getBoundsOf(anchorView, rect);
    195 
    196         int anchorX = rect.left + sPopupWindowOverlap;
    197         int anchorY = (rect.top + rect.bottom) / 2;
    198 
    199         int width = (int) (getWidth() * MAX_WIDTH_RATIO + .5);
    200         int height = (int) (getHeight() * MAX_HEIGHT_RATIO + .5);
    201 
    202         mPopupWindow.measure(
    203                 MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
    204                 MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
    205 
    206         width = mPopupWindow.getMeasuredWidth();
    207         height = mPopupWindow.getMeasuredHeight();
    208 
    209         int xoffset = Math.max(anchorX - width, 0);
    210         int yoffset = Math.max(0, anchorY - height / 2);
    211 
    212         if (yoffset + height > getHeight()) {
    213             yoffset = getHeight() - height;
    214         }
    215         mPopupWindow.setAnchorPosition(anchorY - yoffset);
    216         mPopupWindow.layout(
    217                 xoffset, yoffset, xoffset + width, yoffset + height);
    218     }
    219 
    220     private void showPopupWindow(GLView anchorView) {
    221         layoutPopupWindow(anchorView);
    222         mPopupWindow.popup();
    223         mSharedPrefs.registerOnSharedPreferenceChangeListener(
    224                 mSharedPreferenceChangeListener);
    225         if (mListener != null) {
    226             mListener.onPopupWindowVisibilityChanged(GLView.VISIBLE);
    227         }
    228     }
    229 
    230     private void hidePopupWindow() {
    231         mPopupWindow.popoff();
    232         // Unregister is important to avoid leaking activities.
    233         // ComboPreference.sMap->ComboPreference->HeadUpDisplay->Activity
    234         mSharedPrefs.unregisterOnSharedPreferenceChangeListener(
    235                 mSharedPreferenceChangeListener);
    236         if (mListener != null) {
    237             mListener.onPopupWindowVisibilityChanged(GLView.INVISIBLE);
    238         }
    239     }
    240 
    241     private void scheduleDeactiviateIndicatorBar() {
    242         mHandler.removeMessages(DESELECT_INDICATOR);
    243         mHandler.sendEmptyMessageDelayed(
    244                 DESELECT_INDICATOR, POPUP_WINDOW_TIMEOUT);
    245         mHandler.removeMessages(DEACTIVATE_INDICATOR_BAR);
    246         mHandler.sendEmptyMessageDelayed(
    247                 DEACTIVATE_INDICATOR_BAR, INDICATOR_BAR_TIMEOUT);
    248     }
    249 
    250     public void setOrientation(int orientation) {
    251         GLRootView root = getGLRootView();
    252         if (root != null) {
    253             synchronized (root) {
    254                 setOrientationLocked(orientation);
    255             }
    256         } else {
    257             setOrientationLocked(orientation);
    258         }
    259     }
    260 
    261     private void setOrientationLocked(int orientation) {
    262         mOrientation = orientation;
    263         mIndicatorBar.setOrientation(orientation);
    264         if (mPopupWindow == null) return;
    265         if (mPopupWindow.getVisibility() == GLView.VISIBLE) {
    266             Animation alpha = new AlphaAnimation(0.2f, 1);
    267             alpha.setDuration(250);
    268             mPopupWindow.startAnimation(alpha);
    269             scheduleDeactiviateIndicatorBar();
    270         }
    271         mPopupWindow.setOrientation(orientation);
    272     }
    273 
    274     private void initializePopupWindow(Context context) {
    275         mPopupWindow = new PopupWindow();
    276         mPopupWindow.setBackground(
    277                 new NinePatchTexture(context, R.drawable.menu_popup));
    278         mPopupWindow.setAnchor(new ResourceTexture(
    279                 context, R.drawable.menu_popup_triangle), sPopupTriangleOffset);
    280         mPopupWindow.setVisibility(GLView.INVISIBLE);
    281         mPopupWindow.setOrientation(mOrientation);
    282         addComponent(mPopupWindow);
    283     }
    284 
    285     @Override
    286     protected boolean dispatchTouchEvent(MotionEvent event) {
    287         if (mEnabled && super.dispatchTouchEvent(event)) {
    288             scheduleDeactiviateIndicatorBar();
    289             return true;
    290         }
    291         return false;
    292     }
    293 
    294     public void setEnabled(boolean enabled) {
    295         // The mEnabled variable is not related to the rendering thread, so we
    296         // don't need to synchronize on the GLRootView.
    297         if (mEnabled == enabled) return;
    298         mEnabled = enabled;
    299     }
    300 
    301     @Override
    302     protected boolean onTouch(MotionEvent event) {
    303         if (mPopupWindow == null
    304                 || mPopupWindow.getVisibility() == GLView.INVISIBLE) {
    305             return false;
    306         }
    307 
    308         switch (event.getAction()) {
    309             case MotionEvent.ACTION_UP:
    310                 hidePopupWindow();
    311                 mIndicatorBar.setSelectedIndex(IndicatorBar.INDEX_NONE);
    312                 mIndicatorBar.setActivated(false);
    313                 break;
    314         }
    315         return true;
    316     }
    317 
    318     protected static ListPreference[] getListPreferences(
    319             PreferenceGroup group, String ... prefKeys) {
    320         ArrayList<ListPreference> list = new ArrayList<ListPreference>();
    321         for (String key : prefKeys) {
    322             ListPreference pref = group.findPreference(key);
    323             if (pref != null && pref.getEntries().length > 0) {
    324                 list.add(pref);
    325             }
    326         }
    327         return list.toArray(new ListPreference[list.size()]);
    328     }
    329 
    330     protected BasicIndicator addIndicator(
    331             Context context, PreferenceGroup group, String key) {
    332         IconListPreference iconPref =
    333                 (IconListPreference) group.findPreference(key);
    334         if (iconPref == null) return null;
    335         BasicIndicator indicator = new BasicIndicator(context, iconPref);
    336         mIndicatorBar.addComponent(indicator);
    337         return indicator;
    338     }
    339 
    340     protected void initializeIndicatorBar(
    341             Context context, PreferenceGroup group) {
    342         mIndicatorBar = new IndicatorBar();
    343 
    344         mIndicatorBar.setBackground(new NinePatchTexture(
    345                 context, R.drawable.ic_viewfinder_iconbar));
    346         mIndicatorBar.setHighlight(new ColorTexture(COLOR_ICONBAR_HIGHLIGHT));
    347         addComponent(mIndicatorBar);
    348         mIndicatorBar.setOnItemSelectedListener(new IndicatorBarListener());
    349     }
    350 
    351     private class IndicatorBarListener
    352             implements IndicatorBar.OnItemSelectedListener {
    353 
    354         public void onItemSelected(GLView view, int position) {
    355 
    356             AbstractIndicator indicator = (AbstractIndicator) view;
    357             if (mPopupWindow == null) {
    358                 initializePopupWindow(getGLRootView().getContext());
    359             }
    360             mPopupWindow.setContent(indicator.getPopupContent());
    361 
    362             if (mPopupWindow.getVisibility() == GLView.VISIBLE) {
    363                 layoutPopupWindow(indicator);
    364             } else {
    365                 showPopupWindow(indicator);
    366             }
    367         }
    368 
    369         public void onNothingSelected() {
    370             hidePopupWindow();
    371         }
    372     }
    373 
    374     public boolean collapse() {
    375         // We don't need to synchronize on GLRootView, since both the
    376         // <code>isActivated()</code> and rendering thread are read-only to
    377         // the variables inside.
    378         if (!mIndicatorBar.isActivated()) return false;
    379         mHandler.removeMessages(DESELECT_INDICATOR);
    380         mHandler.removeMessages(DEACTIVATE_INDICATOR_BAR);
    381         GLRootView root = getGLRootView();
    382         if (root != null) {
    383             synchronized (root) {
    384                 mIndicatorBar.setSelectedIndex(IndicatorBar.INDEX_NONE);
    385                 mIndicatorBar.setActivated(false);
    386             }
    387         } else {
    388             mIndicatorBar.setSelectedIndex(IndicatorBar.INDEX_NONE);
    389             mIndicatorBar.setActivated(false);
    390         }
    391         return true;
    392     }
    393 
    394     public void setListener(Listener listener) {
    395         // No synchronization: mListener won't be accessed in rendering thread
    396         mListener = listener;
    397     }
    398 
    399     public void restorePreferences(final Parameters param) {
    400         // Do synchronization in "reloadPreferences()"
    401 
    402         OnSharedPreferenceChangeListener l =
    403                 mSharedPreferenceChangeListener;
    404         // Unregister the listener since "upgrade preference" will
    405         // change bunch of preferences. We can handle them with one
    406         // onSharedPreferencesChanged();
    407         mSharedPrefs.unregisterOnSharedPreferenceChangeListener(l);
    408         Context context = getGLRootView().getContext();
    409         Editor editor = mSharedPrefs.edit();
    410         editor.clear();
    411         editor.apply();
    412         CameraSettings.upgradeAllPreferences(mSharedPrefs);
    413         CameraSettings.initialCameraPictureSize(context, param);
    414         reloadPreferences();
    415         if (mListener != null) {
    416             mListener.onSharedPreferencesChanged();
    417         }
    418         mSharedPrefs.registerOnSharedPreferenceChangeListener(l);
    419     }
    420 
    421     public void reloadPreferences() {
    422         GLRootView root = getGLRootView();
    423         if (root != null) {
    424             synchronized (root) {
    425                 mPreferenceGroup.reloadValue();
    426                 mIndicatorBar.reloadPreferences();
    427             }
    428         } else {
    429             mPreferenceGroup.reloadValue();
    430             mIndicatorBar.reloadPreferences();
    431         }
    432     }
    433 }
    434