Initial changes to make folders accessible

Change-Id: Id8c9cbc0ef200967c3b966fbccfdfde184d4211c
diff --git a/src/com/android/launcher2/DragLayer.java b/src/com/android/launcher2/DragLayer.java
index 28ef6a5..aa49ca1 100644
--- a/src/com/android/launcher2/DragLayer.java
+++ b/src/com/android/launcher2/DragLayer.java
@@ -32,6 +32,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
@@ -65,6 +67,8 @@
     private int[] mDropViewPos = new int[2];
     private float mDropViewScale;
     private float mDropViewAlpha;
+    private boolean mHoverPointClosesFolder = false;
+    private Rect mHitRect = new Rect();
 
     /**
      * Used to create a new DragLayer from XML.
@@ -89,6 +93,22 @@
         return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
     }
 
+    private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
+        getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
+        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
+        getDescendantRectRelativeToSelf(folder, mHitRect);
+        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
+            return true;
+        }
+        return false;
+    }
+
     private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
         Rect hitRect = new Rect();
         int x = (int) ev.getX();
@@ -110,15 +130,14 @@
         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
         if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) {
             if (currentFolder.isEditingName()) {
-                getDescendantRectRelativeToSelf(currentFolder.getEditTextRegion(), hitRect);
-                if (!hitRect.contains(x, y)) {
+                if (!isEventOverFolderTextRegion(currentFolder, ev)) {
                     currentFolder.dismissEditingName();
                     return true;
                 }
             }
 
             getDescendantRectRelativeToSelf(currentFolder, hitRect);
-            if (!hitRect.contains(x, y)) {
+            if (!isEventOverFolder(currentFolder, ev)) {
                 mLauncher.closeFolder();
                 return true;
             }
@@ -138,6 +157,63 @@
     }
 
     @Override
+    public boolean onInterceptHoverEvent(MotionEvent ev) {
+        Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
+        if (currentFolder == null) {
+            return false;
+        } else {
+            if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
+                final int action = ev.getAction();
+                boolean isOverFolder;
+                switch (action) {
+                    case MotionEvent.ACTION_HOVER_ENTER:
+                        isOverFolder = isEventOverFolder(currentFolder, ev);
+                        if (!isOverFolder) {
+                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
+                            mHoverPointClosesFolder = true;
+                            return true;
+                        } else if (isOverFolder) {
+                            mHoverPointClosesFolder = false;
+                        } else {
+                            return true;
+                        }
+                    case MotionEvent.ACTION_HOVER_MOVE:
+                        isOverFolder = isEventOverFolder(currentFolder, ev);
+                        if (!isOverFolder && !mHoverPointClosesFolder) {
+                            sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
+                            mHoverPointClosesFolder = true;
+                            return true;
+                        } else if (isOverFolder) {
+                            mHoverPointClosesFolder = false;
+                        } else {
+                            return true;
+                        }
+                }
+            }
+        }
+        return false;
+    }
+
+    private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
+        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
+            int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
+            AccessibilityEvent event = AccessibilityEvent.obtain(
+                    AccessibilityEvent.TYPE_VIEW_FOCUSED);
+            onInitializeAccessibilityEvent(event);
+            event.getText().add(mContext.getString(stringId));
+            AccessibilityManager.getInstance(mContext).sendAccessibilityEvent(event);
+        }
+    }
+
+    @Override
+    public boolean onHoverEvent(MotionEvent ev) {
+        // If we've received this, we've already done the necessary handling
+        // in onInterceptHoverEvent. Return true to consume the event.
+        return false;
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent ev) {
         boolean handled = false;
         int action = ev.getAction();
diff --git a/src/com/android/launcher2/Folder.java b/src/com/android/launcher2/Folder.java
index 019ac70..33b5de1 100644
--- a/src/com/android/launcher2/Folder.java
+++ b/src/com/android/launcher2/Folder.java
@@ -37,6 +37,8 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.view.inputmethod.EditorInfo;
@@ -95,7 +97,6 @@
     private Alarm mReorderAlarm = new Alarm();
     private Alarm mOnExitAlarm = new Alarm();
     private int mFolderNameHeight;
-    private Rect mHitRect = new Rect();
     private Rect mTempRect = new Rect();
     private boolean mDragInProgress = false;
     private boolean mDeleteFolderOnDropCompleted = false;
@@ -240,9 +241,14 @@
         mFolderName.setHint(sHintText);
         // Convert to a string here to ensure that no other state associated with the text field
         // gets saved.
-        mInfo.setTitle(mFolderName.getText().toString());
+        String newTitle = mFolderName.getText().toString();
+        mInfo.setTitle(newTitle);
         LauncherModel.updateItemInDatabase(mLauncher, mInfo);
 
+        if (commit) {
+            sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+                    String.format(mContext.getString(R.string.folder_renamed), newTitle));
+        }
         // In order to clear the focus from the text field, we set the focus on ourself. This
         // ensures that every time the field is clicked, focus is gained, giving reliable behavior.
         requestFocus();
@@ -283,6 +289,12 @@
         mFolderIcon = icon;
     }
 
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        // When the folder gets focus, we don't want to announce the list of items.
+        return true;
+    }
+
     /**
      * @return the FolderInfo object associated with this folder
      */
@@ -398,6 +410,9 @@
         oa.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
+                sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+                        String.format(mContext.getString(R.string.folder_opened),
+                        mContent.getCountX(), mContent.getCountY()));
                 mState = STATE_ANIMATING;
             }
             @Override
@@ -415,6 +430,15 @@
         oa.start();
     }
 
+    private void sendCustomAccessibilityEvent(int type, String text) {
+        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            AccessibilityEvent event = AccessibilityEvent.obtain(type);
+            onInitializeAccessibilityEvent(event);
+            event.getText().add(text);
+            AccessibilityManager.getInstance(mContext).sendAccessibilityEvent(event);
+        }
+    }
+
     private void setFocusOnFirstChild() {
         View firstChild = mContent.getChildAt(0, 0);
         if (firstChild != null) {
@@ -460,6 +484,8 @@
             }
             @Override
             public void onAnimationStart(Animator animation) {
+                sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+                        mContext.getString(R.string.folder_closed));
                 mState = STATE_ANIMATING;
             }
         });
diff --git a/src/com/android/launcher2/FolderIcon.java b/src/com/android/launcher2/FolderIcon.java
index 8945076..546f4b5 100644
--- a/src/com/android/launcher2/FolderIcon.java
+++ b/src/com/android/launcher2/FolderIcon.java
@@ -32,6 +32,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.ImageView;
@@ -122,19 +123,18 @@
         icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
         icon.mFolderName.setText(folderInfo.title);
         icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background);
-        icon.mPreviewBackground.setContentDescription(folderInfo.title);
 
         icon.setTag(folderInfo);
         icon.setOnClickListener(launcher);
         icon.mInfo = folderInfo;
         icon.mLauncher = launcher;
-
+        icon.setContentDescription(String.format(launcher.getString(R.string.folder_name_format),
+                folderInfo.title));
         Folder folder = Folder.fromXml(launcher);
         folder.setDragController(launcher.getDragController());
         folder.setFolderIcon(icon);
         folder.bind(folderInfo);
         icon.mFolder = folder;
-        Resources res = launcher.getResources();
 
         icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon);
         folderInfo.addListener(icon);
@@ -587,6 +587,7 @@
 
     public void onTitleChanged(CharSequence title) {
         mFolderName.setText(title.toString());
-        mPreviewBackground.setContentDescription(title.toString());
+        setContentDescription(String.format(mContext.getString(R.string.folder_name_format),
+                title));
     }
 }
diff --git a/src/com/android/launcher2/LauncherApplication.java b/src/com/android/launcher2/LauncherApplication.java
index 755168c..6c8ba24 100644
--- a/src/com/android/launcher2/LauncherApplication.java
+++ b/src/com/android/launcher2/LauncherApplication.java
@@ -25,6 +25,8 @@
 import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.os.Handler;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
 
 import java.lang.ref.WeakReference;
 
@@ -40,7 +42,8 @@
         super.onCreate();
 
         // set sIsScreenXLarge and sScreenDensity *before* creating icon cache
-        final int screenSize = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
+        final int screenSize = getResources().getConfiguration().screenLayout &
+                Configuration.SCREENLAYOUT_SIZE_MASK;
         sIsScreenLarge = screenSize == Configuration.SCREENLAYOUT_SIZE_LARGE ||
             screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE;
         sScreenDensity = getResources().getDisplayMetrics().density;