1/*
2 * Copyright (C) 2012 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.googlecode.eyesfree.braille.selfbraille;
18
19import android.os.Bundle;
20import android.os.Parcel;
21import android.os.Parcelable;
22import android.view.View;
23import android.view.accessibility.AccessibilityNodeInfo;
24
25/**
26 * Represents what should be shown on the braille display for a
27 * part of the accessibility node tree.
28 */
29public class WriteData implements Parcelable {
30
31    private static final String PROP_SELECTION_START = "selectionStart";
32    private static final String PROP_SELECTION_END = "selectionEnd";
33
34    private AccessibilityNodeInfo mAccessibilityNodeInfo;
35    private CharSequence mText;
36    private Bundle mProperties = Bundle.EMPTY;
37
38    /**
39     * Returns a new {@link WriteData} instance for the given {@code view}.
40     */
41    public static WriteData forView(View view) {
42        AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(view);
43        WriteData writeData = new WriteData();
44        writeData.mAccessibilityNodeInfo = node;
45        return writeData;
46    }
47
48    public AccessibilityNodeInfo getAccessibilityNodeInfo() {
49        return mAccessibilityNodeInfo;
50    }
51
52    /**
53     * Sets the text to be displayed when the accessibility node associated
54     * with this instance has focus.  If this method is not called (or
55     * {@code text} is {@code null}), this client relinquishes control over
56     * this node.
57     */
58    public WriteData setText(CharSequence text) {
59        mText = text;
60        return this;
61    }
62
63    public CharSequence getText() {
64        return mText;
65    }
66
67    /**
68     * Sets the start position in the text of a text selection or cursor that
69     * should be marked on the display.  A negative value (the default) means
70     * no selection will be added.
71     */
72    public WriteData setSelectionStart(int v) {
73        writableProperties().putInt(PROP_SELECTION_START, v);
74        return this;
75    }
76
77    /**
78     * @see {@link #setSelectionStart}.
79     */
80    public int getSelectionStart() {
81        return mProperties.getInt(PROP_SELECTION_START, -1);
82    }
83
84    /**
85     * Sets the end of the text selection to be marked on the display.  This
86     * value should only be non-negative if the selection start is
87     * non-negative.  If this value is <= the selection start, the selection
88     * is a cursor.  Otherwise, the selection covers the range from
89     * start(inclusive) to end (exclusive).
90     *
91     * @see {@link android.text.Selection}.
92     */
93    public WriteData setSelectionEnd(int v) {
94        writableProperties().putInt(PROP_SELECTION_END, v);
95        return this;
96    }
97
98    /**
99     * @see {@link #setSelectionEnd}.
100     */
101    public int getSelectionEnd() {
102        return mProperties.getInt(PROP_SELECTION_END, -1);
103    }
104
105    private Bundle writableProperties() {
106        if (mProperties == Bundle.EMPTY) {
107            mProperties = new Bundle();
108        }
109        return mProperties;
110    }
111
112    /**
113     * Checks constraints on the fields that must be satisfied before sending
114     * this instance to the self braille service.
115     * @throws IllegalStateException
116     */
117    public void validate() throws IllegalStateException {
118        if (mAccessibilityNodeInfo == null) {
119            throw new IllegalStateException(
120                "Accessibility node info can't be null");
121        }
122        int selectionStart = getSelectionStart();
123        int selectionEnd = getSelectionEnd();
124        if (mText == null) {
125            if (selectionStart > 0 || selectionEnd > 0) {
126                throw new IllegalStateException(
127                    "Selection can't be set without text");
128            }
129        } else {
130            if (selectionStart < 0 && selectionEnd >= 0) {
131                throw new IllegalStateException(
132                    "Selection end without start");
133            }
134            int textLength = mText.length();
135            if (selectionStart > textLength || selectionEnd > textLength) {
136                throw new IllegalStateException("Selection out of bounds");
137            }
138        }
139    }
140
141    // For Parcelable support.
142
143    public static final Parcelable.Creator<WriteData> CREATOR =
144        new Parcelable.Creator<WriteData>() {
145            @Override
146            public WriteData createFromParcel(Parcel in) {
147                return new WriteData(in);
148            }
149
150            @Override
151            public WriteData[] newArray(int size) {
152                return new WriteData[size];
153            }
154        };
155
156    @Override
157    public int describeContents() {
158        return 0;
159    }
160
161    /**
162     * {@inheritDoc}
163     * <strong>Note:</strong> The {@link AccessibilityNodeInfo} will be
164     * recycled by this method, don't try to use this more than once.
165     */
166    @Override
167    public void writeToParcel(Parcel out, int flags) {
168        mAccessibilityNodeInfo.writeToParcel(out, flags);
169        // The above call recycles the node, so make sure we don't use it
170        // anymore.
171        mAccessibilityNodeInfo = null;
172        out.writeString(mText.toString());
173        out.writeBundle(mProperties);
174    }
175
176    private WriteData() {
177    }
178
179    private WriteData(Parcel in) {
180        mAccessibilityNodeInfo =
181                AccessibilityNodeInfo.CREATOR.createFromParcel(in);
182        mText = in.readString();
183        mProperties = in.readBundle();
184    }
185}
186