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