1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher3.accessibility;
18
19import android.content.Context;
20import android.graphics.Rect;
21import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
22import android.text.TextUtils;
23import android.view.View;
24
25import com.android.launcher3.AppInfo;
26import com.android.launcher3.CellLayout;
27import com.android.launcher3.FolderInfo;
28import com.android.launcher3.ItemInfo;
29import com.android.launcher3.Launcher;
30import com.android.launcher3.R;
31import com.android.launcher3.ShortcutInfo;
32import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DragType;
33import com.android.launcher3.dragndrop.DragLayer;
34
35/**
36 * Implementation of {@link DragAndDropAccessibilityDelegate} to support DnD on workspace.
37 */
38public class WorkspaceAccessibilityHelper extends DragAndDropAccessibilityDelegate {
39
40    private final Rect mTempRect = new Rect();
41    private final int[] mTempCords = new int[2];
42
43    public WorkspaceAccessibilityHelper(CellLayout layout) {
44        super(layout);
45    }
46
47    /**
48     * Find the virtual view id corresponding to the top left corner of any drop region by which
49     * the passed id is contained. For an icon, this is simply
50     */
51    @Override
52    protected int intersectsValidDropTarget(int id) {
53        int mCountX = mView.getCountX();
54        int mCountY = mView.getCountY();
55
56        int x = id % mCountX;
57        int y = id / mCountX;
58        LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
59
60        if (dragInfo.dragType == DragType.WIDGET && !mView.acceptsWidget()) {
61            return INVALID_POSITION;
62        }
63
64        if (dragInfo.dragType == DragType.WIDGET) {
65            // For a widget, every cell must be vacant. In addition, we will return any valid
66            // drop target by which the passed id is contained.
67            boolean fits = false;
68
69            // These represent the amount that we can back off if we hit a problem. They
70            // get consumed as we move up and to the right, trying new regions.
71            int spanX = dragInfo.info.spanX;
72            int spanY = dragInfo.info.spanY;
73
74            for (int m = 0; m < spanX; m++) {
75                for (int n = 0; n < spanY; n++) {
76
77                    fits = true;
78                    int x0 = x - m;
79                    int y0 = y - n;
80
81                    if (x0 < 0 || y0 < 0) continue;
82
83                    for (int i = x0; i < x0 + spanX; i++) {
84                        if (!fits) break;
85                        for (int j = y0; j < y0 + spanY; j++) {
86                            if (i >= mCountX || j >= mCountY || mView.isOccupied(i, j)) {
87                                fits = false;
88                                break;
89                            }
90                        }
91                    }
92                    if (fits) {
93                        return x0 + mCountX * y0;
94                    }
95                }
96            }
97            return INVALID_POSITION;
98        } else {
99            // For an icon, we simply check the view directly below
100            View child = mView.getChildAt(x, y);
101            if (child == null || child == dragInfo.item) {
102                // Empty cell. Good for an icon or folder.
103                return id;
104            } else if (dragInfo.dragType != DragType.FOLDER) {
105                // For icons, we can consider cells that have another icon or a folder.
106                ItemInfo info = (ItemInfo) child.getTag();
107                if (info instanceof AppInfo || info instanceof FolderInfo ||
108                        info instanceof ShortcutInfo) {
109                    return id;
110                }
111            }
112            return INVALID_POSITION;
113        }
114    }
115
116    @Override
117    protected String getConfirmationForIconDrop(int id) {
118        int x = id % mView.getCountX();
119        int y = id / mView.getCountX();
120        LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
121
122        View child = mView.getChildAt(x, y);
123        if (child == null || child == dragInfo.item) {
124            return mContext.getString(R.string.item_moved);
125        } else {
126            ItemInfo info = (ItemInfo) child.getTag();
127            if (info instanceof AppInfo || info instanceof ShortcutInfo) {
128                return mContext.getString(R.string.folder_created);
129
130            } else if (info instanceof FolderInfo) {
131                return mContext.getString(R.string.added_to_folder);
132            }
133        }
134        return "";
135    }
136
137    @Override
138    protected void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) {
139        super.onPopulateNodeForVirtualView(id, node);
140
141
142        // ExploreByTouchHelper does not currently handle view scale.
143        // Update BoundsInScreen to appropriate value.
144        DragLayer dragLayer = Launcher.getLauncher(mView.getContext()).getDragLayer();
145        mTempCords[0] = mTempCords[1] = 0;
146        float scale = dragLayer.getDescendantCoordRelativeToSelf(mView, mTempCords);
147
148        node.getBoundsInParent(mTempRect);
149        mTempRect.left = mTempCords[0] + (int) (mTempRect.left * scale);
150        mTempRect.right = mTempCords[0] + (int) (mTempRect.right * scale);
151        mTempRect.top = mTempCords[1] + (int) (mTempRect.top * scale);
152        mTempRect.bottom = mTempCords[1] + (int) (mTempRect.bottom * scale);
153        node.setBoundsInScreen(mTempRect);
154    }
155
156    @Override
157    protected String getLocationDescriptionForIconDrop(int id) {
158        int x = id % mView.getCountX();
159        int y = id / mView.getCountX();
160        LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
161
162        View child = mView.getChildAt(x, y);
163        if (child == null || child == dragInfo.item) {
164            return mView.getItemMoveDescription(x, y);
165        } else {
166            return getDescriptionForDropOver(child, mContext);
167        }
168    }
169
170    public static String getDescriptionForDropOver(View overChild, Context context) {
171        ItemInfo info = (ItemInfo) overChild.getTag();
172        if (info instanceof ShortcutInfo) {
173            return context.getString(R.string.create_folder_with, info.title);
174        } else if (info instanceof FolderInfo) {
175            if (TextUtils.isEmpty(info.title)) {
176                // Find the first item in the folder.
177                FolderInfo folder = (FolderInfo) info;
178                ShortcutInfo firstItem = null;
179                for (ShortcutInfo shortcut : folder.contents) {
180                    if (firstItem == null || firstItem.rank > shortcut.rank) {
181                        firstItem = shortcut;
182                    }
183                }
184
185                if (firstItem != null) {
186                    return context.getString(R.string.add_to_folder_with_app, firstItem.title);
187                }
188            }
189            return context.getString(R.string.add_to_folder, info.title);
190        }
191        return "";
192    }
193}
194