android:layout_height="wrap_content"
android:text="@string/btn_filmstrip_toggle"
android:contentDescription="@string/accessibility_filmstrip_toggle"/>
+ <Button
+ android:id="@+id/accessibility_zoom_minus_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/btn_zoom_minus"
+ android:visibility="gone"
+ android:contentDescription="@string/accessibility_mode_zoom_minus"/>
+ <Button
+ android:id="@+id/accessibility_zoom_plus_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/btn_zoom_plus"
+ android:visibility="gone"
+ android:contentDescription="@string/accessibility_mode_zoom_plus"/>
</LinearLayout>
</FrameLayout>
<!-- Text on the button that toggles the filmstrip. [CHAR LIMIT=20] -->
<string name="btn_filmstrip_toggle">Filmstrip</string>
+ <!-- Content description of the button that zooms in. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_mode_zoom_plus">Zoom in</string>
+
+ <!-- Text on the button that zooms in [CHAR LIMIT=20] -->
+ <string name="btn_zoom_plus">Z+</string>
+
+ <!-- Content description of the button that zooms out. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_mode_zoom_minus">Zoom out</string>
+
+ <!-- Text on the button that zooms out [CHAR LIMIT=20] -->
+ <string name="btn_zoom_minus">Z-</string>
+
+ <!-- Accessibility message spoken by TalkBack informing user of the current zoom value [CHAR LIMIT=NONE] -->
+ <string name="accessibility_zoom_announcement">Zoom value is %.1f</string>
+
<!-- Default text for a button that can be toggled on and off. -->
<string name="capital_on">ON</string>
<!-- Default text for a button that can be toggled on and off. -->
--- /dev/null
+package com.android.camera;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.Context;
+import android.support.v4.view.accessibility.AccessibilityManagerCompat;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.Button;
+import com.android.camera.debug.Log;
+import com.android.camera.ui.MainActivityLayout;
+import com.android.camera.ui.PreviewOverlay;
+import com.android.camera.util.AndroidServices;
+import com.android.camera2.R;
+
+import java.util.List;
+
+/**
+ * AccessibilityUtil provides methods for use when the device is in
+ * accessibility mode
+ */
+public class AccessibilityUtil {
+ private final static Log.Tag TAG = new Log.Tag("AccessibilityUtil");
+ private static final float MIN_ZOOM = 1f;
+
+ // Filters for Google accessibility services
+ private static final String ACCESSIBILITY_PACKAGE_NAME_PREFIX = "com.google";
+ private PreviewOverlay mPreviewOverlay;
+ private Button mZoomPlusButton;
+ private Button mZoomMinusButton;
+ private float mMaxZoom;
+
+ private View.OnClickListener zoomInListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ float currentZoom = mPreviewOverlay.zoomIn(view, mMaxZoom);
+
+ // Zooming in implies that you must be able to subsequently zoom
+ // out.
+ mZoomMinusButton.setEnabled(true);
+
+ // Make sure it doesn't go above max zoom.
+ if (currentZoom == mMaxZoom) {
+ mZoomPlusButton.setEnabled(false);
+ }
+ }
+ };
+
+ private View.OnClickListener zoomOutListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ float currentZoom = mPreviewOverlay.zoomOut(view, mMaxZoom);
+
+ // Zooming out implies that you must be able to subsequently zoom
+ // in.
+ mZoomPlusButton.setEnabled(true);
+
+ // Make sure it doesn't go below min zoom.
+ if (currentZoom == MIN_ZOOM) {
+ mZoomMinusButton.setEnabled(false);
+ }
+ }
+ };
+
+ public AccessibilityUtil(PreviewOverlay previewOverlay, View view) {
+ mPreviewOverlay = previewOverlay;
+ mZoomPlusButton = (Button) view.findViewById(R.id.accessibility_zoom_plus_button);
+ mZoomMinusButton = (Button) view.findViewById(R.id.accessibility_zoom_minus_button);
+ mZoomPlusButton.setOnClickListener(zoomInListener);
+ mZoomMinusButton.setOnClickListener(zoomOutListener);
+ }
+
+ /**
+ * Enables the zoom UI with zoom in/zoom out buttons.
+ *
+ * @param maxZoom is maximum zoom on the given device
+ */
+ public void showZoomUI(float maxZoom) {
+ mMaxZoom = maxZoom;
+ mZoomPlusButton.setVisibility(View.VISIBLE);
+ mZoomMinusButton.setVisibility(View.VISIBLE);
+ mZoomMinusButton.setEnabled(false);
+ }
+
+ public void hideZoomUI() {
+ mZoomPlusButton.setVisibility(View.GONE);
+ mZoomMinusButton.setVisibility(View.GONE);
+ }
+
+ /**
+ * Returns the accessibility manager.
+ */
+ private android.view.accessibility.AccessibilityManager getAccessibilityManager() {
+ return AndroidServices.instance().provideAccessibilityManager();
+ }
+
+ /**
+ * Returns whether touch exploration is enabled.
+ */
+ private boolean isTouchExplorationEnabled() {
+ android.view.accessibility.AccessibilityManager accessibilityManager = getAccessibilityManager();
+ return accessibilityManager.isTouchExplorationEnabled();
+ }
+
+ /**
+ * Checks whether Google accessibility services are enabled, including
+ * TalkBack, Switch Access, and others
+ *
+ * @return boolean
+ */
+ private boolean containsGoogleAccessibilityService() {
+ android.view.accessibility.AccessibilityManager accessibilityManager = getAccessibilityManager();
+ List<AccessibilityServiceInfo> enabledServices =
+ accessibilityManager
+ .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ if (enabledServices != null) {
+ for (AccessibilityServiceInfo enabledService : enabledServices) {
+ String serviceId = enabledService.getId();
+ if (serviceId != null && serviceId.startsWith(ACCESSIBILITY_PACKAGE_NAME_PREFIX)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether a touch exploration is enabled or a Google accessibility
+ * service is enabled.
+ */
+ public boolean isAccessibilityEnabled() {
+ return isTouchExplorationEnabled()
+ || containsGoogleAccessibilityService();
+ }
+}
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
+import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ShareActionProvider;
private void openModule(CameraModule module) {
module.init(this, isSecureCamera(), isCaptureIntent());
module.hardResetSettings(mSettingsManager);
+ // Hide accessibility zoom UI by default. Modules will enable it themselves if required.
+ getCameraAppUI().hideAccessibilityZoomUI();
if (!mPaused) {
module.resume();
UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
// state, ... ).
mAppController.getCameraAppUI().enableModeOptions();
mAppController.setShutterEnabled(true);
+ mAppController.getCameraAppUI().showAccessibilityZoomUI(
+ mCameraCharacteristics.getAvailableMaxDigitalZoom());
mHdrPlusEnabled = mStickyGcamCamera || mAppController.getSettingsManager().getInteger(
SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS) == 1;
private float mMaxZoom = 1f;
/** Set up listener to receive zoom changes from View and send to module. */
- private final OnZoomChangedListener mZoomChancedListener = new OnZoomChangedListener() {
+ private final OnZoomChangedListener mZoomChangedListener = new OnZoomChangedListener() {
@Override
public void onZoomValueChanged(float ratio) {
mListener.onZoomRatioChanged(ratio);
*/
public void initializeZoom(float maxZoom) {
mMaxZoom = maxZoom;
- mPreviewOverlay.setupZoom(mMaxZoom, 0, mZoomChancedListener);
+ mPreviewOverlay.setupZoom(mMaxZoom, 0, mZoomChangedListener);
}
@Override
import com.android.camera.module.ModuleController;
import com.android.camera.one.OneCamera;
import com.android.camera.one.OneCameraAccessException;
-import com.android.camera.one.config.OneCameraFeatureConfig;
import com.android.camera.remote.RemoteCameraModule;
import com.android.camera.settings.CameraPictureSizesCacher;
import com.android.camera.settings.Keys;
mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
}
mAppController.addPreviewAreaSizeChangedListener(mUI);
+ mAppController.getCameraAppUI().showAccessibilityZoomUI(
+ mCameraCapabilities.getMaxZoomRatio());
CameraProvider camProvider = mActivity.getCameraProvider();
if (camProvider == null) {
import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.Bitmap;
-import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.Camera.Face;
import com.android.camera.ui.PreviewOverlay;
import com.android.camera.ui.PreviewStatusListener;
import com.android.camera.ui.focus.FocusRing;
-import com.android.camera.util.CameraUtil;
import com.android.camera2.R;
import com.android.ex.camera2.portability.CameraAgent;
import com.android.ex.camera2.portability.CameraCapabilities;
}
mCameraDevice = cameraProxy;
mCameraCapabilities = mCameraDevice.getCapabilities();
+ mAppController.getCameraAppUI().showAccessibilityZoomUI(
+ mCameraCapabilities.getMaxZoomRatio());
mCameraSettings = mCameraDevice.getSettings();
mFocusAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA);
mMeteringAreaSupported =
package com.android.camera.app;
-import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
+import com.android.camera.AccessibilityUtil;
import com.android.camera.AnimationManager;
import com.android.camera.ButtonManager;
import com.android.camera.CaptureLayoutHelper;
import com.android.camera.ui.CaptureAnimationOverlay;
import com.android.camera.ui.GridLines;
import com.android.camera.ui.MainActivityLayout;
-import com.android.camera.ui.MarginDrawable;
import com.android.camera.ui.ModeListView;
import com.android.camera.ui.ModeTransitionView;
import com.android.camera.ui.PreviewOverlay;
import com.android.camera.widget.RoundedThumbnailView;
import com.android.camera2.R;
-import java.util.List;
-
/**
* CameraAppUI centralizes control of views shared across modules. Whereas module
* specific views will be handled in each Module UI. For example, we can now
private View mModeOptionsToggle;
private final RoundedThumbnailView mRoundedThumbnailView;
private final CaptureLayoutHelper mCaptureLayoutHelper;
- private boolean mAccessibilityEnabled;
private final View mAccessibilityAffordances;
+ private AccessibilityUtil mAccessibilityUtil;
private boolean mDisableAllUserInteractions;
/** Whether to prevent capture indicator from being triggered. */
// Show UI that is meant to only be used when spoken feedback is
// enabled.
- mAccessibilityEnabled = isSpokenFeedbackAccessibilityEnabled();
mAccessibilityAffordances.setVisibility(
- (!mIsCaptureIntent && mAccessibilityEnabled) ? View.VISIBLE : View.GONE);
- }
-
- /**
- * @return Whether any spoken feedback accessibility feature is currently
- * enabled.
- */
- private boolean isSpokenFeedbackAccessibilityEnabled() {
- AccessibilityManager accessibilityManager = AndroidServices.instance()
- .provideAccessibilityManager();
- List<AccessibilityServiceInfo> infos = accessibilityManager
- .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
- return infos != null && !infos.isEmpty();
+ (!mIsCaptureIntent && mAccessibilityUtil.isAccessibilityEnabled()) ? View.VISIBLE
+ : View.GONE);
}
/**
mModeListView.onMenuPressed();
}
+ public void showAccessibilityZoomUI(float maxZoom) {
+ mAccessibilityUtil.showZoomUI(maxZoom);
+ }
+
+ public void hideAccessibilityZoomUI() {
+ mAccessibilityUtil.hideZoomUI();
+ }
+
/**
* A cover view showing the mode theme color and mode icon will be visible on
* top of preview until preview is ready (i.e. camera preview is started and
mAccessibilityAffordances.setVisibility(View.GONE);
} else {
setIndicatorBottomBarWrapperVisible(true);
- if (!mIsCaptureIntent && mAccessibilityEnabled) {
+ if (!mIsCaptureIntent && mAccessibilityUtil.isAccessibilityEnabled()) {
mAccessibilityAffordances.setVisibility(View.VISIBLE);
} else {
mAccessibilityAffordances.setVisibility(View.GONE);
mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay);
mPreviewOverlay.setOnTouchListener(new MyTouchListener());
mPreviewOverlay.setOnPreviewTouchedListener(mModeOptionsOverlay);
+ mAccessibilityUtil = new AccessibilityUtil(mPreviewOverlay, mAccessibilityAffordances);
mCaptureOverlay = (CaptureAnimationOverlay)
mCameraRootView.findViewById(R.id.capture_overlay);
import android.view.ScaleGestureDetector;
import android.view.View;
+import android.widget.Button;
import com.android.camera.debug.Log;
import com.android.camera2.R;
implements PreviewStatusListener.PreviewAreaChangedListener {
public static final float ZOOM_MIN_RATIO = 1.0f;
+ private static final int NUM_ZOOM_LEVELS = 7;
+ private static final float MIN_ZOOM = 1f;
private static final Log.Tag TAG = new Log.Tag("PreviewOverlay");
private OnZoomChangedListener mZoomListener = null;
private OnPreviewTouchedListener mOnPreviewTouchedListener;
+ /** Maximum zoom; intialize to 1.0 (disabled) */
+ private float mMaxZoom = MIN_ZOOM;
+ /**
+ * Current zoom value in accessibility mode, ranging from MIN_ZOOM to
+ * mMaxZoom.
+ */
+ private float mCurrA11yZoom = MIN_ZOOM;
+ /**
+ * Current zoom level ranging between 1 and NUM_ZOOM_LEVELS. Each level is
+ * associated with a discrete zoom value.
+ */
+ private int mCurrA11yZoomLevel = 1;
+
public interface OnZoomChangedListener {
/**
* This gets called when a zoom is detected and started.
mZoomProcessor.setupZoom(zoomMaxRatio, zoom);
}
+ /**
+ * uZooms camera in when in accessibility mode.
+ *
+ * @param view is the current view
+ * @param maxZoom is the maximum zoom value on the given device
+ * @return float representing the current zoom value
+ */
+ public float zoomIn(View view, float maxZoom) {
+ mCurrA11yZoomLevel++;
+ mMaxZoom = maxZoom;
+ mCurrA11yZoom = getZoomAtLevel(mCurrA11yZoomLevel);
+ mZoomListener.onZoomValueChanged(mCurrA11yZoom);
+ view.announceForAccessibility(String.format(
+ view.getResources().
+ getString(R.string.accessibility_zoom_announcement), mCurrA11yZoom));
+ return mCurrA11yZoom;
+ }
+
+ /**
+ * Zooms camera out when in accessibility mode.
+ *
+ * @param view is the current view
+ * @param maxZoom is the maximum zoom value on the given device
+ * @return float representing the current zoom value
+ */
+ public float zoomOut(View view, float maxZoom) {
+ mCurrA11yZoomLevel--;
+ mMaxZoom = maxZoom;
+ mCurrA11yZoom = getZoomAtLevel(mCurrA11yZoomLevel);
+ mZoomListener.onZoomValueChanged(mCurrA11yZoom);
+ view.announceForAccessibility(String.format(
+ view.getResources().
+ getString(R.string.accessibility_zoom_announcement), mCurrA11yZoom));
+ return mCurrA11yZoom;
+ }
+
+ /**
+ * Method used in accessibility mode. Ensures that there are evenly spaced
+ * zoom values ranging from MIN_ZOOM to NUM_ZOOM_LEVELS
+ *
+ * @param level is the zoom level being computed in the range
+ * @return the zoom value at the given level
+ */
+ private float getZoomAtLevel(int level) {
+ return (MIN_ZOOM + ((level - 1) * ((mMaxZoom - MIN_ZOOM) / (NUM_ZOOM_LEVELS - 1))));
+ }
+
@Override
public boolean onTouchEvent(MotionEvent m) {
// Pass the touch events to scale detector and gesture detector
mZoomListener = null;
mGestureDetector = null;
mTouchListener = null;
+ mCurrA11yZoomLevel = 1;
+ mCurrA11yZoom = MIN_ZOOM;
}
/**