/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.camera; import android.content.Context; import android.content.SharedPreferences; import android.graphics.PixelFormat; import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.util.Log; import android.view.Display; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ListView; import android.widget.RadioButton; import android.widget.TextView; import android.widget.AdapterView.OnItemClickListener; import java.util.ArrayList; import java.util.HashMap; import java.util.regex.Pattern; import java.util.regex.Matcher; /** * The on-screen setting menu. * *

Please reference to {@code android.widget.ZoomButtonsController} for * detail information about adding window to WindowManager. */ public class OnScreenSettings { @SuppressWarnings("unused") private static final String TAG = "OnScreenSettings"; private static final int MSG_POST_SET_VISIBLE = 1; private static final Pattern TITLE_PATTERN = Pattern.compile("(.*)\\s*\\((.+)\\)"); /** * A callback to be invoked when the on-screen menu's visibility changes. */ public interface OnVisibilityChangedListener { public void onVisibilityChanged(boolean visibility); } private LayoutParams mContainerLayoutParams; private final Context mContext; private final Container mContainer; private final WindowManager mWindowManager; private final View mOwnerView; private ListView mMainMenu; private ListView mSubMenu; private boolean mIsVisible = false; private OnVisibilityChangedListener mVisibilityListener; private MainMenuAdapter mMainAdapter; private final LayoutInflater mInflater; private Runnable mRestoreRunner; // We store the override values here. For a given preference, // if the mapping value of the preference key is not null, we will // use the value in this map instead of the value read from the preference // // This is design for the scene mode, for example, in the scene mode // "Action", the focus mode will become "infinite" no matter what in the // preference settings. So, we need to put a {pref_camera_focusmode_key, // "infinite"} entry in this map. private HashMap mOverride = new HashMap(); private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_POST_SET_VISIBLE: setVisible(true); break; } } }; public OnScreenSettings(View ownerView) { mContext = ownerView.getContext(); mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mOwnerView = ownerView; mContainer = createContainer(); } public void clearSettings() { SharedPreferences.Editor editor = PreferenceManager .getDefaultSharedPreferences(mContext).edit(); editor.clear(); editor.commit(); mOverride.clear(); } public void setRestoreRunner(Runnable restorer) { mRestoreRunner = restorer; } public boolean isVisible() { return mIsVisible; } public void setOnVisibilityChangedListener( OnVisibilityChangedListener listener) { mVisibilityListener = listener; } public void setVisible(boolean visible) { mHandler.removeMessages(MSG_POST_SET_VISIBLE); if (visible) { if (mOwnerView.getWindowToken() == null) { /* * We need a window token to show ourselves, maybe the owner's * window hasn't been created yet but it will have been by the * time the looper is idle, so post the setVisible(true) call. */ mHandler.sendEmptyMessage(MSG_POST_SET_VISIBLE); return; } } if (mIsVisible == visible) { return; } mIsVisible = visible; if (visible) { // Invalid the main adapter before show up, so it would be like // a new created list if (mMainAdapter != null) mMainAdapter.notifyDataSetInvalidated(); if (mContainerLayoutParams.token == null) { mContainerLayoutParams.token = mOwnerView.getWindowToken(); } mSubMenu.setVisibility(View.INVISIBLE); mMainMenu.setVisibility(View.VISIBLE); mWindowManager.addView(mContainer, mContainerLayoutParams); updateLayout(); } else { // Reset the two menus mWindowManager.removeView(mContainer); } if (mVisibilityListener != null) { mVisibilityListener.onVisibilityChanged(mIsVisible); } } // Override the preference settings, if value == null, then disable the // override. public void overrideSettings(String key, String value) { if (value == null) { if (mOverride.remove(key) != null && mMainAdapter != null) { mMainAdapter.notifyDataSetChanged(); } } else { if (mOverride.put(key, value) == null && mMainAdapter != null) { mMainAdapter.notifyDataSetChanged(); } } } public void updateLayout() { // if the mOwnerView is detached from window then skip. if (mOwnerView.getWindowToken() == null) return; Display display = mWindowManager.getDefaultDisplay(); mContainerLayoutParams.x = 0; mContainerLayoutParams.y = 0; mContainerLayoutParams.width = display.getWidth() / 2; mContainerLayoutParams.height = display.getHeight(); if (mIsVisible) { mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams); } } private void showSubMenu() { Util.slideOut(mMainMenu, Util.DIRECTION_LEFT); Util.slideIn(mSubMenu, Util.DIRECTION_RIGHT); mSubMenu.requestFocus(); } private void closeSubMenu() { Util.slideOut(mSubMenu, Util.DIRECTION_RIGHT); Util.slideIn(mMainMenu, Util.DIRECTION_LEFT); } private Container createContainer() { LayoutParams lp = new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); lp.flags = LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; lp.gravity = Gravity.TOP | Gravity.LEFT; lp.height = LayoutParams.WRAP_CONTENT; lp.width = LayoutParams.WRAP_CONTENT; lp.type = LayoutParams.TYPE_APPLICATION_PANEL; lp.format = PixelFormat.OPAQUE; lp.windowAnimations = R.style.Animation_OnScreenMenu; mContainerLayoutParams = lp; Container container = new Container(mContext); container.setLayoutParams(lp); mInflater.inflate(R.layout.on_screen_menu, container); mMainMenu = (ListView) container.findViewById(R.id.menu_view); mSubMenu = (ListView) container.findViewById(R.id.sub_menu); container.findViewById(R.id.btn_gripper) .setOnTouchListener(new GripperTouchListener()); return container; } private class GripperTouchListener implements View.OnTouchListener { public boolean onTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: return true; case MotionEvent.ACTION_UP: setVisible(false); return true; } return false; } } private boolean onContainerKey(KeyEvent event) { switch (event.getKeyCode()) { case KeyEvent.KEYCODE_CAMERA: case KeyEvent.KEYCODE_FOCUS: case KeyEvent.KEYCODE_BACK: case KeyEvent.KEYCODE_MENU: if (event.getAction() == KeyEvent.ACTION_UP) { setVisible(false); return true; } } return false; } // Add the preference and it's children recursively to the given list. So // that we can show the preference (and it's children) in the list view. private static void addPreference( CameraPreference preference, ArrayList list) { list.add(preference); if (preference instanceof PreferenceGroup) { PreferenceGroup group = (PreferenceGroup) preference; for (int i = 0, n = group.size(); i < n; ++i) { CameraPreference child = group.get(i); addPreference(child, list); } } } public void setPreferenceGroup(PreferenceGroup group) { ArrayList list = new ArrayList(); // We don't want the root group added to the list. So, we add the // children of the root here for (int i = 0, n = group.size(); i < n; ++i) { addPreference(group.get(i), list); } mMainAdapter = new MainMenuAdapter(mContext, list); mMainMenu.setAdapter(mMainAdapter); mMainMenu.setOnItemClickListener(mMainAdapter); } private View inflateIfNeed( View view, int resource, ViewGroup root, boolean attachToRoot) { if (view != null) return view; return mInflater.inflate(resource, root, attachToRoot); } private class MainMenuAdapter extends BaseAdapter implements OnItemClickListener { private final ArrayList mPreferences; public MainMenuAdapter( Context context, ArrayList preferences) { mPreferences = preferences; } public void onItemClick( AdapterView parent, View view, int position, long id) { // If not the last item (restore settings) if (position < mPreferences.size()) { CameraPreference preference = mPreferences.get(position); SubMenuAdapter adapter = new SubMenuAdapter( mContext, (ListPreference) preference); mSubMenu.setAdapter(adapter); mSubMenu.setOnItemClickListener(adapter); showSubMenu(); } else { MenuHelper.confirmAction(mContext, mContext.getString(R.string.confirm_restore_title), mContext.getString(R.string.confirm_restore_message), mRestoreRunner); } } public View getView(int position, View convertView, ViewGroup parent) { // The last item in the view (i.e., restore settings) if (position == mPreferences.size()) { convertView = inflateIfNeed(convertView, R.layout.on_screen_menu_list_item, parent, false); ((TextView) convertView.findViewById(R.id.title)).setText( mContext.getString(R.string.pref_restore_title)); ((TextView) convertView.findViewById(R.id.summary)).setText( mContext.getString(R.string.pref_restore_detail)); // Make sure we didn't set focusable. Otherwise, it will // consume all the events. See more detail in the comments // below. convertView.setFocusable(false); return convertView; } CameraPreference preference = mPreferences.get(position); if (preference instanceof PreferenceGroup) { convertView = inflateIfNeed(convertView, R.layout.on_screen_menu_header, parent, false); PreferenceGroup group = (PreferenceGroup) preference; ((TextView) convertView.findViewById( R.id.title)).setText(group.getTitle()); } else { convertView = inflateIfNeed(convertView, R.layout.on_screen_menu_list_item, parent, false); String override = mOverride.get( ((ListPreference) preference).getKey()); TextView title = (TextView) convertView.findViewById(R.id.title); title.setText(preference.getTitle()); title.setEnabled(override == null); TextView summary = (TextView) convertView.findViewById(R.id.summary); summary.setText(override == null ? ((ListPreference) preference).getEntry() : override); summary.setEnabled(override == null); // A little trick here, making the view focusable will eat // both touch/key events on the view and thus make it looks // like disabled. convertView.setFocusable(override != null); } return convertView; } @Override public boolean areAllItemsEnabled() { return false; } @Override public boolean isEnabled(int position) { // The last item (restore default) if (position == mPreferences.size()) return true; CameraPreference preference = mPreferences.get(position); return !(preference instanceof PreferenceGroup); } public int getCount() { // The last one is "restore default" return mPreferences.size() + 1; } public Object getItem(int position) { return null; } public long getItemId(int position) { return position; } @Override public int getItemViewType(int position) { if (position == mPreferences.size()) return 1; CameraPreference pref = mPreferences.get(position); if (pref instanceof PreferenceGroup) return 0; if (pref instanceof ListPreference) return 1; throw new IllegalStateException(); } @Override public int getViewTypeCount() { // we have two types, see getItemViewType() return 2; } @Override public boolean hasStableIds() { return true; } @Override public boolean isEmpty() { return mPreferences.isEmpty(); } } private class SubMenuAdapter extends BaseAdapter implements OnItemClickListener { private final ListPreference mPreference; private final IconListPreference mIconPreference; public SubMenuAdapter(Context context, ListPreference preference) { mPreference = preference; mIconPreference = (preference instanceof IconListPreference) ? (IconListPreference) preference : null; } public View getView(int position, View convertView, ViewGroup parent) { CharSequence entry[] = mPreference.getEntries(); if (position == 0) { convertView = inflateIfNeed(convertView, R.layout.on_screen_menu_header, parent, false); ((TextView) convertView.findViewById( R.id.title)).setText(mPreference.getTitle()); } else { int index = position - 1; convertView = inflateIfNeed(convertView, R.layout.on_screen_submenu_item, parent, false); boolean checked = mPreference.getValue().equals( mPreference.getEntryValues()[index]); String title = entry[index].toString(); String detail = null; // Handle the title of format "Title (details)". We extract the // details from the title message for better UI layout. The // detail will be shown in second line with a smaller font. Matcher matcher = TITLE_PATTERN.matcher(title); if (matcher.matches()) { title = matcher.group(1); detail = matcher.group(2); } ((TextView) convertView .findViewById(R.id.title)).setText(title); TextView detailView = (TextView) convertView.findViewById(R.id.summary); if (detail == null) { detailView.setVisibility(View.GONE); } else { detailView.setVisibility(View.VISIBLE); detailView.setText(detail); } ((RadioButton) convertView.findViewById( R.id.radio_button)).setChecked(checked); ImageView icon = (ImageView) convertView.findViewById(R.id.icon); if (mIconPreference != null) { icon.setVisibility(View.VISIBLE); icon.setImageDrawable( mIconPreference.getIcons()[position - 1]); } else { icon.setVisibility(View.GONE); } } return convertView; } @Override public boolean areAllItemsEnabled() { return false; } @Override public boolean isEnabled(int position) { return getItemViewType(position) != 0; } public int getCount() { // add one for the header return mPreference.getEntries().length + 1; } public Object getItem(int position) { return null; } public long getItemId(int position) { return position; } @Override public int getItemViewType(int position) { return position == 0 ? 0 : 1; } @Override public int getViewTypeCount() { return 2; } @Override public boolean hasStableIds() { return true; } public void onItemClick( AdapterView parent, View view, int position, long id) { CharSequence values[] = mPreference.getEntryValues(); int idx = mPreference.findIndexOfValue(mPreference.getValue()); if (idx != position - 1) { mPreference.setValueIndex(position - 1); notifyDataSetChanged(); mMainAdapter.notifyDataSetChanged(); return; } // Close the sub menu when user presses the original option. closeSubMenu(); } } private class Container extends FrameLayout { public Container(Context context) { super(context); } @Override public boolean onTouchEvent(MotionEvent event) { if (super.onTouchEvent(event)) return true; if (event.getAction() == MotionEvent.ACTION_DOWN) { setVisible(false); return true; } return false; } /* * Need to override this to intercept the key events. Otherwise, we * would attach a key listener to the container but its superclass * ViewGroup gives it to the focused View instead of calling the key * listener, and so we wouldn't get the events. */ @Override public boolean dispatchKeyEvent(KeyEvent event) { return onContainerKey(event) ? true : super.dispatchKeyEvent(event); } } }