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