在Android平台上,设置菜单有两种方式:通过XML布局来实现和通过Menu.add方法实现。Camera系统中的菜单可以说是通过XML布局来实现的,但它的实现过程并非像一般文章里介绍的那样简单。所以,在此我就以设置capture时picture大小为例,将Camera系统中菜单的设置流程作以简单介绍。
1、资源文件分析
在packages/apps/Camera/res/xml/有两个xml文件,分别为Camera_preferences.xml和Video_preferences.xml。这两个文件中列举了Camera和Video Camera两种模式下的不同菜单信息。以Camera_preferences.xml为例,其中关于picture大小的菜单项信息为:
<ListPreference camera:key="pref_camera_picturesize_key"
camera:title="@string/pref_camera_picturesize_title"
camera:entries="@array/pref_camera_picturesize_entries"
camera:entryValues="@array/pref_camera_picturesize_entryvalues" />
pref_camera_picturesize_title的定义在packages/apps/Camera/res/values/strings.xml中,其中有如下的定义:
- <!--
Settings screen, Picture size title
-->
<item>@string/pref_camera_picturesize_entry_2592x1936</item>
<item>@string/pref_camera_picturesize_entry_2592x1936</item>
<item>@string/pref_camera_picturesize_entry_2048x1536</item>
<item>@string/pref_camera_picturesize_entry_1600x1200</item>
<item>@string/pref_camera_picturesize_entry_1024x768</item>
<item>@string/pref_camera_picturesize_entry_320x240</item>
</string-array>
pref_camera_picturesize_entryvalues的定义也在
packages/apps/Camera/res/values/arrays.xml中,其中有如下的定义:
-<string-array name="pref_camera_picturesize_entryvalues" translatable="false">
<item>2592x1944</item>
<item>2592x1936</item>
<item>2048x1536</item>
<item>1600x1200</item>
<item>1024x768</item>
<item>320x240</item>
</string-array>
由这些定义,我们可以看出,Android的Camera应用程序支持五种图片大小。但是硬件并非都支持这些大小。所以最终的菜单中只会显示这五种大小当中底层硬件所支持的,如果硬件支持的大小与其中任何一种都不匹配,则不会显示出“Picture size”菜单。
2、菜单的创建
在文件Packages/apps/camera/src/com/android/camera/Camera.java中,函数onCreateOptionsMenu()用来创建Camera系统的菜单。其具体定义如下:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
if (mIsImageCaptureIntent) {
// No options menu for attach mode.
return false;
} else {
addBaseMenuItems(menu);
}
return true;
}
在非Video Camera模式下,mIsImageCaptureIntent为true,此处将不做任何处理。当mIsImageCaptureIntent为false时,即Video Camera模式下,将调用函数addBaseMenuItems()来创建Video Camera的菜单。Picture大小的菜单不会出现在Video Camera模式下,所以就不对addBaseMenuItems()函数分析了。
既然这里没有创建Camera模式下的菜单,那Camera模式下的菜单在哪里创建的了?我们接着分析。
在Camera.java文件中定义了类MainHandler,它里面只有一个成员函数handleMessage(),它用来处理Camera应用程序中的message。
类MainHandler的定义如下:
/**
* This Handler is used to post message back onto the main thread of the
* application
*/
private class MainHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case RESTART_PREVIEW: {
restartPreview();
if (mJpegPictureCallbackTime != 0) {
long now = System.currentTimeMillis();
mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
Log.v(TAG, "mJpegCallbackFinishTime = "
+ mJpegCallbackFinishTime + "ms");
mJpegPictureCallbackTime = 0;
}
break;
}
case CLEAR_SCREEN_DELAY: {
getWindow().clearFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
break;
}
case FIRST_TIME_INIT: {
initializeFirstTime();
break;
}
case SET_CAMERA_PARAMETERS_WHEN_IDLE: {
setCameraParametersWhenIdle(0);
break;
}
}
}
Message FIRST_TIME_INIT应该在类Camera初始化时就会被处理,我是这样猜的,只是还没有找到具体的code。initializeFirstTime()的实现如下所示:
// Snapshots can only be taken after this is called. It should be called
// once only. We could have done these things in onCreate() but we want to
// make preview screen appear as soon as possible.
private void initializeFirstTime() {
……
mFirstTimeInitialized = true;
changeHeadUpDisplayState();
addIdleHandler();
}
在函数的结束部位有一个函数changeHeadUpDisplayState(),它的定义如下:
private void changeHeadUpDisplayState() {
// If the camera resumes behind the lock screen, the orientation
// will be portrait. That causes OOM when we try to allocation GPU
// memory for the GLSurfaceView again when the orientation changes. So,
// we delayed initialization of HeadUpDisplay until the orientation
// becomes landscape.
Configuration config = getResources().getConfiguration();
if (config.orientation == Configuration.ORIENTATION_LANDSCAPE
&& !mPausing && mFirstTimeInitialized) {
if (mGLRootView == null) initializeHeadUpDisplay();
} else if (mGLRootView != null) {
finalizeHeadUpDisplay();
}
}
此时mFirstTimeInitialized已经为true,当其他条件也满足时将进入函数initializeHeadUpDisplay(),它的具体定义为:
private void initializeHeadUpDisplay() {
FrameLayout frame = (FrameLayout) findViewById(R.id.frame);
mGLRootView = new GLRootView(this);
frame.addView(mGLRootView);
mHeadUpDisplay = new CameraHeadUpDisplay(this);
CameraSettings settings = new CameraSettings(this, mInitialParams);
mHeadUpDisplay.initialize(this,
settings.getPreferenceGroup(R.xml.camera_preferences));
mHeadUpDisplay.setListener(new MyHeadUpDisplayListener());
mHeadUpDisplay.setOrientation(mLastOrientation);
if (mParameters.isZoomSupported()) {
……
}
updateSceneModeInHud();
mGLRootView.setContentPane(mHeadUpDisplay);
}
语句mHeadUpDisplay.initialize(this, settings.getPreferenceGroup(R.xml.camera_preferences));中的R.xml.camera_preferences指的就是camera_preference.xml资源文件。
接下来我们将对settings.getPreferenceGroup(),mHeadUpDisplay.initialize()和MyHeadUpDisplayListener做以介绍。
函数settings.getPreferenceGroup()定义在文件Packages/apps/camera/src/com/android/camera/CameraSetting.java中。其具体定义为:
public PreferenceGroup getPreferenceGroup(int preferenceRes) {
PreferenceInflater inflater = new PreferenceInflater(mContext);
PreferenceGroup group =
(PreferenceGroup) inflater.inflate(preferenceRes);
initPreference(group);
return group;
}
函数inflater.inflate(preferenceRes)将camera_preference.xml文件中的菜单信息存储到PreferenceGroup中。接着函数initPreference()对这些信息做了处理,其定义为:
private void initPreference(PreferenceGroup group) {
ListPreference videoQuality = group.findPreference(KEY_VIDEO_QUALITY);
ListPreference pictureSize = group.findPreference(KEY_PICTURE_SIZE);
ListPreference whiteBalance = group.findPreference(KEY_WHITE_BALANCE);
……
// Since the screen could be loaded from different resources, we need
// to check if the preference is available here
if (videoQuality != null) {
// Modify video duration settings.
// The first entry is for MMS video duration, and we need to fill
// in the device-dependent value (in seconds).
CharSequence[] entries = videoQuality.getEntries();
CharSequence[] values = videoQuality.getEntryValues();
for (int i = 0; i < entries.length; ++i) {
if (VIDEO_QUALITY_MMS.equals(values[i])) {
entries[i] = entries[i].toString().replace(
"30", Integer.toString(MMS_VIDEO_DURATION));
break;
}
}
}
// Filter out unsupported settings / options
if (pictureSize != null) {
filterUnsupportedOptions(group, pictureSize, sizeListToStringList(
mParameters.getSupportedPictureSizes()));
}
if (whiteBalance != null) {
filterUnsupportedOptions(group,
whiteBalance, mParameters.getSupportedWhiteBalance());
}
……
}
其中,宏定义KEY_PICTURE_SIZE定义在文件
Packages/apps/camera/src/com/android/camera/CameraSetting.java中,具体定义为:
public static final String KEY_PICTURE_SIZE = "pref_camera_picturesize_key";
该值与camera_preference.xml中的KEY值匹配。语句ListPreference pictureSize = group.findPreference(KEY_PICTURE_SIZE);将picture的大小信息存储在了结构体ListPreference pictureSize中。
语句
if (pictureSize != null) {
filterUnsupportedOptions(group, pictureSize, sizeListToStringList(
mParameters.getSupportedPictureSizes()));
}
mParameters.getSupportedPictureSizes()获取了硬件Camera所支持的picture大小信息,我在Camera HAL层的实现中,在函数initDefaultParameters()中设置了硬件所支持的picture大小。CODE如下:
CameraParameters p;
p.set(“picture-size-values”, “1600x1200,1024x768,640x480”);
函数filterUnsupportedOptions()会将Camera应用和硬件支持的picture大小中两者匹配的大小存储在group中。
函数mHeadUpDisplay.initialize()定义在文件
Packages/apps/camera/src/com/android/camera/ui/HeadUpDisplay.java中,其具体定义为:
public void initialize(Context context, PreferenceGroup preferenceGroup) {
mPreferenceGroup = preferenceGroup;
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
mSharedPrefs.registerOnSharedPreferenceChangeListener(
mSharedPreferenceChangeListener);
initializeIndicatorBar(context, preferenceGroup);
}
其中函数initializeIndicatorBar()定义如下:
protected void initializeIndicatorBar(
Context context, PreferenceGroup group) {
mIndicatorBar = new IndicatorBar();
mIndicatorBar.setBackground(new NinePatchTexture(
context, R.drawable.ic_viewfinder_iconbar));
mIndicatorBar.setHighlight(new NinePatchTexture(
context, R.drawable.ic_viewfinder_iconbar_highlight));
addComponent(mIndicatorBar);
mIndicatorBar.setOnItemSelectedListener(new IndicatorBarListener());
}
函数addComponent()将group中的picture大小添加到了菜单中。至此,菜单的创建就告一段落。下面我们介绍一下菜单的事件。
3、菜单的监听事件
当菜单被点击时,菜单的监听事件就会监听到该事件,并作出相应的处理。监听事件定义在文件Packages/apps/camera/src/com/android/camera/Camera.java中。其具体定义为:
private class MyHeadUpDisplayListener implements HeadUpDisplay.Listener {
// The callback functions here will be called from the GLThread. So,
// we need to post these runnables to the main thread
public void onSharedPreferencesChanged() {
mHandler.post(new Runnable() {
public void run() {
Camera.this.onSharedPreferenceChanged();
}
});
}
public void onRestorePreferencesClicked() {
mHandler.post(new Runnable() {
public void run() {
Camera.this.onRestorePreferencesClicked();
}
});
}
public void onPopupWindowVisibilityChanged(int visibility) {
}
}
函数onSharedPreferenceChanged()定义为:
private void onSharedPreferenceChanged() {
// ignore the events after "onPause()"
if (mPausing) return;
boolean recordLocation;
synchronized (mPreferences) {
recordLocation = RecordLocationPreference.get(
mPreferences, getContentResolver());
mQuickCapture = getQuickCaptureSettings();
}
if (mRecordLocation != recordLocation) {
mRecordLocation = recordLocation;
if (mRecordLocation) {
startReceivingLocationUpdates();
} else {
stopReceivingLocationUpdates();
}
}
setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
}
其中函数setCameraParametersWhenIdle()定义为:
// If the Camera is idle, update the parameters immediately, otherwise
// accumulate them in mUpdateSet and update later.
private void setCameraParametersWhenIdle(int additionalUpdateSet) {
mUpdateSet |= additionalUpdateSet;
if (mCameraDevice == null) {
// We will update all the parameters when we open the device, so
// we don't need to do anything now.
mUpdateSet = 0;
return;
} else if (isCameraIdle()) {
setCameraParameters(mUpdateSet);
mUpdateSet = 0;
} else {
if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
mHandler.sendEmptyMessageDelayed(
SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
}
}
}
函数setCameraParameters()定义为:
// We separate the parameters into several subsets, so we can update only
// the subsets actually need updating. The PREFERENCE set needs extra
// locking because the preference can be changed from GLThread as well.
private void setCameraParameters(int updateSet) {
mParameters = mCameraDevice.getParameters();
if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
updateCameraParametersInitialize();
}
if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
updateCameraParametersZoom();
}
if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
synchronized (mPreferences) {
updateCameraParametersPreference();
}
}
mCameraDevice.setParameters(mParameters);
}
Picture大小的设置属于UPDATE_PARAM_PREFERENCE,所以会调到函数updateCameraParametersPreference(),其定义为:
private void updateCameraParametersPreference() {
// Set picture size.
String pictureSize = mPreferences.getString(
CameraSettings.KEY_PICTURE_SIZE, null);
if (pictureSize == null) {
CameraSettings.initialCameraPictureSize(this, mParameters);
} else {
List<Size> supported = mParameters.getSupportedPictureSizes();
CameraSettings.setCameraPictureSize(
pictureSize, supported, mParameters);
}
// Set the preview frame aspect ratio according to the picture size.
Size size = mParameters.getPictureSize();
PreviewFrameLayout frameLayout =
(PreviewFrameLayout) findViewById(R.id.frame_layout);
frameLayout.setAspectRatio((double) size.width / size.height);
// Set a preview size that is closest to the viewfinder height and has
// the right aspect ratio.
List<Size> sizes = mParameters.getSupportedPreviewSizes();
Size optimalSize = getOptimalPreviewSize(
sizes, (double) size.width / size.height);
if (optimalSize != null) {
mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
}
……
}
函数CameraSettings.initialCameraPictureSize()的定义如下:
public static void initialCameraPictureSize(
Context context, Parameters parameters) {
// When launching the camera app first time, we will set the picture
// size to the first one in the list defined in "arrays.xml" and is also
// supported by the driver.
List<Size> supported = parameters.getSupportedPictureSizes();
if (supported == null) return;
for (String candidate : context.getResources().getStringArray(
R.array.pref_camera_picturesize_entryvalues)) {
if (setCameraPictureSize(candidate, supported, parameters)) {
SharedPreferences.Editor editor = PreferenceManager
.getDefaultSharedPreferences(context).edit();
editor.putString(KEY_PICTURE_SIZE, candidate);
editor.commit();
return;
}
}
Log.e(TAG, "No supported picture size found");
}
函数setCameraPictureSize()定义如下:
public static boolean setCameraPictureSize(
String candidate, List<Size> supported, Parameters parameters) {
int index = candidate.indexOf('x');
if (index == NOT_FOUND) return false;
int width = Integer.parseInt(candidate.substring(0, index));
int height = Integer.parseInt(candidate.substring(index + 1));
for (Size size: supported) {
if (size.width == width && size.height == height) {
parameters.setPictureSize(width, height);
return true;
}
}
return false;
}
监听事件中的onRestorePreferencesClicked()定义如下:
protected void onRestorePreferencesClicked() {
if (mPausing) return;
Runnable runnable = new Runnable() {
public void run() {
mHeadUpDisplay.restorePreferences(mParameters);
}
};
MenuHelper.confirmAction(this,
getString(R.string.confirm_restore_title),
getString(R.string.confirm_restore_message),
runnable);
}
其中restorePreferences()函数定义如下:
public void restorePreferences(final Parameters param) {
getGLRootView().runInGLThread(new Runnable() {
public void run() {
OnSharedPreferenceChangeListener l =
mSharedPreferenceChangeListener;
// Unregister the listener since "upgrade preference" will
// change bunch of preferences. We can handle them with one
// onSharedPreferencesChanged();
mSharedPrefs.unregisterOnSharedPreferenceChangeListener(l);
Context context = getGLRootView().getContext();
synchronized (mSharedPrefs) {
Editor editor = mSharedPrefs.edit();
editor.clear();
editor.commit();
}
CameraSettings.upgradePreferences(mSharedPrefs);
CameraSettings.initialCameraPictureSize(context, param);
reloadPreferences();
if (mListener != null) {
mListener.onSharedPreferencesChanged();
}
mSharedPrefs.registerOnSharedPreferenceChangeListener(l);
}
});
}
函数reloadPreferences()定义如下:
public void reloadPreferences() {
mPreferenceGroup.reloadValue();
mIndicatorBar.reloadPreferences();
}
}