OSDN Git Service

Adding ability to zoom in accessibility mode using TalkBack
authorzafir <zafir@google.com>
Sat, 21 Feb 2015 09:29:22 +0000 (02:29 -0700)
committerzafir <zafir@google.com>
Tue, 3 Mar 2015 18:54:47 +0000 (10:54 -0800)
Bug: 18986078
Bug: 19546523
Change-Id: Ie68234a9a3a0b8b9be870cc674658194c72c84ef

res/layout/camera.xml
res/values/strings.xml
src/com/android/camera/AccessibilityUtil.java [new file with mode: 0644]
src/com/android/camera/CameraActivity.java
src/com/android/camera/CaptureModule.java
src/com/android/camera/CaptureModuleUI.java
src/com/android/camera/PhotoModule.java
src/com/android/camera/PhotoUI.java
src/com/android/camera/VideoModule.java
src/com/android/camera/app/CameraAppUI.java
src/com/android/camera/ui/PreviewOverlay.java

index f5ff32f..704bfc2 100644 (file)
             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>
index 948ae2c..7312541 100644 (file)
     <!-- 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. -->
diff --git a/src/com/android/camera/AccessibilityUtil.java b/src/com/android/camera/AccessibilityUtil.java
new file mode 100644 (file)
index 0000000..155a149
--- /dev/null
@@ -0,0 +1,134 @@
+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();
+    }
+}
index 7e6ba79..93965e5 100644 (file)
@@ -61,6 +61,7 @@ import android.view.View.OnSystemUiVisibilityChangeListener;
 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;
@@ -2503,6 +2504,8 @@ public class CameraActivity extends QuickActivity
     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(),
index d21409e..5fd08ec 100644 (file)
@@ -600,6 +600,8 @@ public class CaptureModule extends CameraModule implements
         // 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;
index e7e6785..cbf1932 100644 (file)
@@ -61,7 +61,7 @@ public class CaptureModuleUI implements PreviewStatusListener.PreviewAreaChanged
     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);
@@ -173,7 +173,7 @@ public class CaptureModuleUI implements PreviewStatusListener.PreviewAreaChanged
      */
     public void initializeZoom(float maxZoom) {
         mMaxZoom = maxZoom;
-        mPreviewOverlay.setupZoom(mMaxZoom, 0, mZoomChancedListener);
+        mPreviewOverlay.setupZoom(mMaxZoom, 0, mZoomChangedListener);
     }
 
     @Override
index 13bc2fa..7d40536 100644 (file)
@@ -56,7 +56,6 @@ import com.android.camera.hardware.HeadingSensor;
 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;
@@ -1410,6 +1409,8 @@ public class PhotoModule
             mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
         }
         mAppController.addPreviewAreaSizeChangedListener(mUI);
+        mAppController.getCameraAppUI().showAccessibilityZoomUI(
+                mCameraCapabilities.getMaxZoomRatio());
 
         CameraProvider camProvider = mActivity.getCameraProvider();
         if (camProvider == null) {
index 226de75..5e5d353 100644 (file)
@@ -19,7 +19,6 @@ package com.android.camera;
 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;
@@ -39,7 +38,6 @@ import com.android.camera.ui.FaceView;
 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;
index 16649e1..572c03d 100644 (file)
@@ -579,6 +579,8 @@ public class VideoModule extends CameraModule
         }
         mCameraDevice = cameraProxy;
         mCameraCapabilities = mCameraDevice.getCapabilities();
+        mAppController.getCameraAppUI().showAccessibilityZoomUI(
+                mCameraCapabilities.getMaxZoomRatio());
         mCameraSettings = mCameraDevice.getSettings();
         mFocusAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA);
         mMeteringAreaSupported =
index 5846f7e..5e8b93d 100644 (file)
 
 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;
@@ -33,9 +31,9 @@ import android.view.TextureView;
 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;
@@ -52,7 +50,6 @@ import com.android.camera.ui.BottomBar;
 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;
@@ -72,8 +69,6 @@ import com.android.camera.widget.ModeOptionsOverlay;
 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
@@ -546,8 +541,8 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
     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. */
@@ -998,21 +993,9 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
 
         // 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);
     }
 
     /**
@@ -1024,6 +1007,14 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
         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
@@ -1071,7 +1062,7 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
             mAccessibilityAffordances.setVisibility(View.GONE);
         } else {
             setIndicatorBottomBarWrapperVisible(true);
-            if (!mIsCaptureIntent && mAccessibilityEnabled) {
+            if (!mIsCaptureIntent && mAccessibilityUtil.isAccessibilityEnabled()) {
                 mAccessibilityAffordances.setVisibility(View.VISIBLE);
             } else {
                 mAccessibilityAffordances.setVisibility(View.GONE);
@@ -1255,6 +1246,7 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
         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);
index 6ff8549..80286a3 100644 (file)
@@ -29,6 +29,7 @@ import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
 import android.view.View;
 
+import android.widget.Button;
 import com.android.camera.debug.Log;
 import com.android.camera2.R;
 
@@ -50,6 +51,8 @@ public class PreviewOverlay extends View
     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");
 
@@ -65,6 +68,19 @@ public class PreviewOverlay extends View
     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.
@@ -110,6 +126,53 @@ public class PreviewOverlay extends View
         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
@@ -174,6 +237,8 @@ public class PreviewOverlay extends View
         mZoomListener = null;
         mGestureDetector = null;
         mTouchListener = null;
+        mCurrA11yZoomLevel = 1;
+        mCurrA11yZoom = MIN_ZOOM;
     }
 
     /**