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