• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
HeadUpDisplay(Context context)118     public HeadUpDisplay(Context context) {
119         initializeStaticVariables(context);
120     }
121 
initializeStaticVariables(Context context)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 {
onPopupWindowVisibilityChanged(int visibility)135         public void onPopupWindowVisibilityChanged(int visibility);
onRestorePreferencesClicked()136         public void onRestorePreferencesClicked();
onSharedPreferencesChanged()137         public void onSharedPreferencesChanged();
138     }
139 
overrideSettings(final String ... keyvalues)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 
overrideSettingsLocked(final String ... keyvalues)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
onLayout( boolean changed, int left, int top, int right, int bottom)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 
initialize(Context context, PreferenceGroup preferenceGroup)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 
layoutPopupWindow(GLView anchorView)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 
showPopupWindow(GLView anchorView)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 
hidePopupWindow()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 
scheduleDeactiviateIndicatorBar()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 
setOrientation(int orientation)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 
setOrientationLocked(int orientation)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 
initializePopupWindow(Context context)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
dispatchTouchEvent(MotionEvent event)286     protected boolean dispatchTouchEvent(MotionEvent event) {
287         if (mEnabled && super.dispatchTouchEvent(event)) {
288             scheduleDeactiviateIndicatorBar();
289             return true;
290         }
291         return false;
292     }
293 
setEnabled(boolean enabled)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
onTouch(MotionEvent event)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 
getListPreferences( PreferenceGroup group, String ... prefKeys)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 
addIndicator( Context context, PreferenceGroup group, String key)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 
initializeIndicatorBar( Context context, PreferenceGroup group)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 
onItemSelected(GLView view, int position)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 
onNothingSelected()369         public void onNothingSelected() {
370             hidePopupWindow();
371         }
372     }
373 
collapse()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 
setListener(Listener listener)394     public void setListener(Listener listener) {
395         // No synchronization: mListener won't be accessed in rendering thread
396         mListener = listener;
397     }
398 
restorePreferences(final Parameters param)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 
reloadPreferences()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