diff options
Diffstat (limited to 'main/thirdparty')
7 files changed, 2285 insertions, 0 deletions
diff --git a/main/thirdparty/android/support/v4/app/FragmentListActivity.java b/main/thirdparty/android/support/v4/app/FragmentListActivity.java new file mode 100644 index 0000000..e3ed42c --- /dev/null +++ b/main/thirdparty/android/support/v4/app/FragmentListActivity.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.support.v4.app; + +import android.os.Bundle; +import android.os.Handler; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListAdapter; +import android.widget.ListView; + +/** + * An activity that displays a list of items by binding to a data source such as + * an array or Cursor, and exposes event handlers when the user selects an item. + * <p> + * FragmentListActivity hosts a {@link android.widget.ListView ListView} object that can + * be bound to different data sources, typically either an array or a Cursor + * holding query results. Binding, screen layout, and row layout are discussed + * in the following sections. + * <p> + * <strong>Screen Layout</strong> + * </p> + * <p> + * FragmentListActivity has a default layout that consists of a single, full-screen list + * in the center of the screen. However, if you desire, you can customize the + * screen layout by setting your own view layout with setContentView() in + * onCreate(). To do this, your own view MUST contain a ListView object with the + * id "@android:id/list" (or {@link android.R.id#list} if it's in code) + * <p> + * Optionally, your custom view can contain another view object of any type to + * display when the list view is empty. This "empty list" notifier must have an + * id "android:empty". Note that when an empty view is present, the list view + * will be hidden when there is no data to display. + * <p> + * The following code demonstrates an (ugly) custom screen layout. It has a list + * with a green background, and an alternate red "no data" message. + * </p> + * + * <pre> + * <?xml version="1.0" encoding="utf-8"?> + * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + * android:orientation="vertical" + * android:layout_width="fill_parent" + * android:layout_height="fill_parent" + * android:paddingLeft="8dp" + * android:paddingRight="8dp"> + * + * <ListView android:id="@id/android:list" + * android:layout_width="fill_parent" + * android:layout_height="fill_parent" + * android:background="#00FF00" + * android:layout_weight="1" + * android:drawSelectorOnTop="false"/> + * + * <TextView android:id="@id/android:empty" + * android:layout_width="fill_parent" + * android:layout_height="fill_parent" + * android:background="#FF0000" + * android:text="No data"/> + * </LinearLayout> + * </pre> + * + * <p> + * <strong>Row Layout</strong> + * </p> + * <p> + * You can specify the layout of individual rows in the list. You do this by + * specifying a layout resource in the ListAdapter object hosted by the activity + * (the ListAdapter binds the ListView to the data; more on this later). + * <p> + * A ListAdapter constructor takes a parameter that specifies a layout resource + * for each row. It also has two additional parameters that let you specify + * which data field to associate with which object in the row layout resource. + * These two parameters are typically parallel arrays. + * </p> + * <p> + * Android provides some standard row layout resources. These are in the + * {@link android.R.layout} class, and have names such as simple_list_item_1, + * simple_list_item_2, and two_line_list_item. The following layout XML is the + * source for the resource two_line_list_item, which displays two data + * fields,one above the other, for each list row. + * </p> + * + * <pre> + * <?xml version="1.0" encoding="utf-8"?> + * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + * android:layout_width="fill_parent" + * android:layout_height="wrap_content" + * android:orientation="vertical"> + * + * <TextView android:id="@+id/text1" + * android:textSize="16sp" + * android:textStyle="bold" + * android:layout_width="fill_parent" + * android:layout_height="wrap_content"/> + * + * <TextView android:id="@+id/text2" + * android:textSize="16sp" + * android:layout_width="fill_parent" + * android:layout_height="wrap_content"/> + * </LinearLayout> + * </pre> + * + * <p> + * You must identify the data bound to each TextView object in this layout. The + * syntax for this is discussed in the next section. + * </p> + * <p> + * <strong>Binding to Data</strong> + * </p> + * <p> + * You bind the FragmentListActivity's ListView object to data using a class that + * implements the {@link android.widget.ListAdapter ListAdapter} interface. + * Android provides two standard list adapters: + * {@link android.widget.SimpleAdapter SimpleAdapter} for static data (Maps), + * and {@link android.widget.SimpleCursorAdapter SimpleCursorAdapter} for Cursor + * query results. + * </p> + * <p> + * The following code from a custom FragmentListActivity demonstrates querying the + * Contacts provider for all contacts, then binding the Name and Company fields + * to a two line row layout in the activity's ListView. + * </p> + * + * <pre> + * public class MyListAdapter extends FragmentListActivity { + * + * @Override + * protected void onCreate(Bundle savedInstanceState){ + * super.onCreate(savedInstanceState); + * + * // We'll define a custom screen layout here (the one shown above), but + * // typically, you could just use the standard FragmentListActivity layout. + * setContentView(R.layout.custom_list_activity_view); + * + * // Query for all people contacts using the {@link android.provider.Contacts.People} convenience class. + * // Put a managed wrapper around the retrieved cursor so we don't have to worry about + * // requerying or closing it as the activity changes state. + * mCursor = this.getContentResolver().query(People.CONTENT_URI, null, null, null, null); + * startManagingCursor(mCursor); + * + * // Now create a new list adapter bound to the cursor. + * // SimpleListAdapter is designed for binding to a Cursor. + * ListAdapter adapter = new SimpleCursorAdapter( + * this, // Context. + * android.R.layout.two_line_list_item, // Specify the row template to use (here, two columns bound to the two retrieved cursor + * rows). + * mCursor, // Pass in the cursor to bind to. + * new String[] {People.NAME, People.COMPANY}, // Array of cursor columns to bind to. + * new int[] {android.R.id.text1, android.R.id.text2}); // Parallel array of which template objects to bind to those columns. + * + * // Bind to our new adapter. + * setListAdapter(adapter); + * } + * } + * </pre> + * + * @see #setListAdapter + * @see android.widget.ListView + */ +public class FragmentListActivity extends FragmentActivity { + /** + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected ListAdapter mAdapter; + /** + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected ListView mList; + + private Handler mHandler = new Handler(); + private boolean mFinishedStart = false; + + private Runnable mRequestFocus = new Runnable() { + @Override + public void run() { + mList.focusableViewAvailable(mList); + } + }; + + /** + * This method will be called when an item in the list is selected. + * Subclasses should override. Subclasses can call + * getListView().getItemAtPosition(position) if they need to access the + * data associated with the selected item. + * + * @param l The ListView where the click happened + * @param v The view that was clicked within the ListView + * @param position The position of the view in the list + * @param id The row id of the item that was clicked + */ + protected void onListItemClick(ListView l, View v, int position, long id) { + } + + /** + * Ensures the list view has been created before Activity restores all + * of the view states. + * + *@see Activity#onRestoreInstanceState(Bundle) + */ + @Override + protected void onRestoreInstanceState(Bundle state) { + ensureList(); + super.onRestoreInstanceState(state); + } + + /** + * Updates the screen state (current list and other views) when the + * content changes. + * + * @see Activity#onContentChanged() + */ + @Override + public void onContentChanged() { + super.onContentChanged(); + View emptyView = findViewById(android.R.id.empty); + mList = (ListView)findViewById(android.R.id.list); + if (mList == null) { + throw new RuntimeException( + "Your content must have a ListView whose id attribute is " + + "'android.R.id.list'"); + } + if (emptyView != null) { + mList.setEmptyView(emptyView); + } + mList.setOnItemClickListener(mOnClickListener); + if (mFinishedStart) { + setListAdapter(mAdapter); + } + mHandler.post(mRequestFocus); + mFinishedStart = true; + } + + /** + * Provide the cursor for the list view. + */ + public void setListAdapter(ListAdapter adapter) { + synchronized (this) { + ensureList(); + mAdapter = adapter; + mList.setAdapter(adapter); + } + } + + /** + * Set the currently selected list item to the specified + * position with the adapter's data + * + * @param position + */ + public void setSelection(int position) { + mList.setSelection(position); + } + + /** + * Get the position of the currently selected list item. + */ + public int getSelectedItemPosition() { + return mList.getSelectedItemPosition(); + } + + /** + * Get the cursor row ID of the currently selected list item. + */ + public long getSelectedItemId() { + return mList.getSelectedItemId(); + } + + /** + * Get the activity's list view widget. + */ + public ListView getListView() { + ensureList(); + return mList; + } + + /** + * Get the ListAdapter associated with this activity's ListView. + */ + public ListAdapter getListAdapter() { + return mAdapter; + } + + private void ensureList() { + if (mList != null) { + return; + } + setContentView(android.R.layout.list_content); + + } + + private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View v, int position, long id) + { + onListItemClick((ListView)parent, v, position, id); + } + }; +} + diff --git a/main/thirdparty/cgeo/org/kxml2/io/KXmlSerializer.java b/main/thirdparty/cgeo/org/kxml2/io/KXmlSerializer.java new file mode 100644 index 0000000..027ff53 --- /dev/null +++ b/main/thirdparty/cgeo/org/kxml2/io/KXmlSerializer.java @@ -0,0 +1,605 @@ +/* + * Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +package cgeo.org.kxml2.io; + +import org.apache.commons.lang3.StringUtils; +import org.xmlpull.v1.XmlSerializer; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.Locale; + +public class KXmlSerializer implements XmlSerializer { + + // static final String UNDEFINED = ":"; + + // BEGIN android-added + /** size (in characters) for the write buffer */ + private static final int WRITE_BUFFER_SIZE = 500; + // END android-added + + // BEGIN android-changed + // (Guarantee that the writer is always buffered.) + private BufferedWriter writer; + // END android-changed + + private boolean pending; + private int auto; + private int depth; + + private String[] elementStack = new String[12]; + //nsp/prefix/name + private int[] nspCounts = new int[4]; + private String[] nspStack = new String[8]; + //prefix/nsp; both empty are "" + private boolean[] indent = new boolean[4]; + private boolean unicode; + private String encoding; + + private final void check(boolean close) throws IOException { + if (!pending) { + return; + } + + depth++; + pending = false; + + if (indent.length <= depth) { + boolean[] hlp = new boolean[depth + 4]; + System.arraycopy(indent, 0, hlp, 0, depth); + indent = hlp; + } + indent[depth] = indent[depth - 1]; + + for (int i = nspCounts[depth - 1]; i < nspCounts[depth]; i++) { + writer.write(' '); + writer.write("xmlns"); + if (!StringUtils.isEmpty(nspStack[i * 2])) { + writer.write(':'); + writer.write(nspStack[i * 2]); + } + else if (StringUtils.isEmpty(getNamespace()) && !StringUtils.isEmpty(nspStack[i * 2 + 1])) { + throw new IllegalStateException("Cannot set default namespace for elements in no namespace"); + } + writer.write("=\""); + writeEscaped(nspStack[i * 2 + 1], '"'); + writer.write('"'); + } + + if (nspCounts.length <= depth + 1) { + int[] hlp = new int[depth + 8]; + System.arraycopy(nspCounts, 0, hlp, 0, depth + 1); + nspCounts = hlp; + } + + nspCounts[depth + 1] = nspCounts[depth]; + // nspCounts[depth + 2] = nspCounts[depth]; + + writer.write(close ? " />" : ">"); + } + + private final void writeEscaped(String s, int quot) throws IOException { + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + switch (c) { + case '\n': + case '\r': + case '\t': + if (quot == -1) { + writer.write(c); + } else { + writer.write("&#"+((int) c)+';'); + } + break; + case '&': + writer.write("&"); + break; + case '>': + writer.write(">"); + break; + case '<': + writer.write("<"); + break; + default: + if (c == quot) { + writer.write(c == '"' ? """ : "'"); + break; + } + // BEGIN android-changed: refuse to output invalid characters + // See http://www.w3.org/TR/REC-xml/#charsets for definition. + // Corrected for c:geo to handle utf-16 codepoint surrogates correctly + // See http://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B10000_to_U.2B10FFFF + // Note: tab, newline, and carriage return have already been + // handled above. + // Check for lead surrogate + if (c >= 0xd800 && c <= 0xdbff) { + + if (i + 1 < s.length()) { + writer.write(s.substring(i, i + 1)); + i++; + break; + } + // if the lead surrogate is at the string end, it's not valid utf-16 + reportInvalidCharacter(c); + } + boolean valid = (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd); + if (!valid) { + reportInvalidCharacter(c); + } + if (unicode || c < 127) { + writer.write(c); + } else { + writer.write("&#" + ((int) c) + ";"); + } + // END android-changed + } + } + } + + // BEGIN android-added + private static void reportInvalidCharacter(char ch) { + throw new IllegalArgumentException("Illegal character (" + Integer.toHexString((int) ch) + ")"); + } + // END android-added + + /* + * private final void writeIndent() throws IOException { + * writer.write("\r\n"); + * for (int i = 0; i < depth; i++) + * writer.write(' '); + * } + */ + + public void docdecl(String dd) throws IOException { + writer.write("<!DOCTYPE"); + writer.write(dd); + writer.write(">"); + } + + public void endDocument() throws IOException { + while (depth > 0) { + endTag(elementStack[depth * 3 - 3], elementStack[depth * 3 - 1]); + } + flush(); + } + + public void entityRef(String name) throws IOException { + check(false); + writer.write('&'); + writer.write(name); + writer.write(';'); + } + + public boolean getFeature(String name) { + //return false; + return ("http://xmlpull.org/v1/doc/features.html#indent-output" + .equals( + name)) + ? indent[depth] + : false; + } + + public String getPrefix(String namespace, boolean create) { + try { + return getPrefix(namespace, false, create); + } catch (IOException e) { + throw new RuntimeException(e.toString()); + } + } + + private final String getPrefix( + String namespace, + boolean includeDefault, + boolean create) + throws IOException { + + for (int i = nspCounts[depth + 1] * 2 - 2; i >= 0; i -= 2) { + if (nspStack[i + 1].equals(namespace) + && (includeDefault || !StringUtils.isEmpty(nspStack[i]))) { + String cand = nspStack[i]; + for (int j = i + 2; j < nspCounts[depth + 1] * 2; j++) { + if (nspStack[j].equals(cand)) { + cand = null; + break; + } + } + if (cand != null) { + return cand; + } + } + } + + if (!create) { + return null; + } + + String prefix; + + if (StringUtils.isEmpty(namespace)) { + prefix = ""; + } else { + do { + prefix = "n" + (auto++); + for (int i = nspCounts[depth + 1] * 2 - 2; i >= 0; i -= 2) { + if (prefix.equals(nspStack[i])) { + prefix = null; + break; + } + } + } while (prefix == null); + } + + boolean p = pending; + pending = false; + setPrefix(prefix, namespace); + pending = p; + return prefix; + } + + public Object getProperty(String name) { + throw new RuntimeException("Unsupported property"); + } + + public void ignorableWhitespace(String s) + throws IOException { + text(s); + } + + public void setFeature(String name, boolean value) { + if ("http://xmlpull.org/v1/doc/features.html#indent-output" + .equals(name)) { + indent[depth] = value; + } else { + throw new RuntimeException("Unsupported Feature"); + } + } + + public void setProperty(String name, Object value) { + throw new RuntimeException( + "Unsupported Property:" + value); + } + + public void setPrefix(String prefix, String namespace) + throws IOException { + + check(false); + if (prefix == null) { + prefix = ""; + } + if (namespace == null) { + namespace = ""; + } + + String defined = getPrefix(namespace, true, false); + + // boil out if already defined + + if (prefix.equals(defined)) { + return; + } + + int pos = (nspCounts[depth + 1]++) << 1; + + if (nspStack.length < pos + 1) { + String[] hlp = new String[nspStack.length + 16]; + System.arraycopy(nspStack, 0, hlp, 0, pos); + nspStack = hlp; + } + + nspStack[pos++] = prefix; + nspStack[pos] = namespace; + } + + public void setOutput(Writer writer) { + // BEGIN android-changed + // Guarantee that the writer is always buffered. + if (writer instanceof BufferedWriter) { + this.writer = (BufferedWriter) writer; + } else { + this.writer = new BufferedWriter(writer, WRITE_BUFFER_SIZE); + } + // END android-changed + + // elementStack = new String[12]; //nsp/prefix/name + //nspCounts = new int[4]; + //nspStack = new String[8]; //prefix/nsp + //indent = new boolean[4]; + + nspCounts[0] = 2; + nspCounts[1] = 2; + nspStack[0] = ""; + nspStack[1] = ""; + nspStack[2] = "xml"; + nspStack[3] = "http://www.w3.org/XML/1998/namespace"; + pending = false; + auto = 0; + depth = 0; + + unicode = false; + } + + public void setOutput(OutputStream os, String encoding) + throws IOException { + if (os == null) { + throw new IllegalArgumentException("os == null"); + } + setOutput(encoding == null + ? new OutputStreamWriter(os) + : new OutputStreamWriter(os, encoding)); + this.encoding = encoding; + if (encoding != null && encoding.toLowerCase(Locale.US).startsWith("utf")) { + unicode = true; + } + } + + public void startDocument(String encoding, Boolean standalone) throws IOException { + writer.write("<?xml version='1.0' "); + + if (encoding != null) { + this.encoding = encoding; + if (encoding.toLowerCase(Locale.US).startsWith("utf")) { + unicode = true; + } + } + + if (this.encoding != null) { + writer.write("encoding='"); + writer.write(this.encoding); + writer.write("' "); + } + + if (standalone != null) { + writer.write("standalone='"); + writer.write( + standalone.booleanValue() ? "yes" : "no"); + writer.write("' "); + } + writer.write("?>"); + } + + public XmlSerializer startTag(String namespace, String name) + throws IOException { + check(false); + + // if (namespace == null) + // namespace = ""; + + if (indent[depth]) { + writer.write("\r\n"); + for (int i = 0; i < depth; i++) { + writer.write(" "); + } + } + + int esp = depth * 3; + + if (elementStack.length < esp + 3) { + String[] hlp = new String[elementStack.length + 12]; + System.arraycopy(elementStack, 0, hlp, 0, esp); + elementStack = hlp; + } + + String prefix = + namespace == null + ? "" + : getPrefix(namespace, true, true); + + if (namespace != null && StringUtils.isEmpty(namespace)) { + for (int i = nspCounts[depth]; i < nspCounts[depth + 1]; i++) { + if (StringUtils.isEmpty(nspStack[i * 2]) && !StringUtils.isEmpty(nspStack[i * 2 + 1])) { + throw new IllegalStateException("Cannot set default namespace for elements in no namespace"); + } + } + } + + elementStack[esp++] = namespace; + elementStack[esp++] = prefix; + elementStack[esp] = name; + + writer.write('<'); + if (!StringUtils.isEmpty(prefix)) { + writer.write(prefix); + writer.write(':'); + } + + writer.write(name); + + pending = true; + + return this; + } + + public XmlSerializer attribute( + String namespace, + String name, + String value) + throws IOException { + if (!pending) { + throw new IllegalStateException("illegal position for attribute"); + } + + // int cnt = nspCounts[depth]; + + if (namespace == null) { + namespace = ""; + } + + // depth--; + // pending = false; + + String prefix = + StringUtils.isEmpty(namespace) + ? "" + : getPrefix(namespace, false, true); + + // pending = true; + // depth++; + + /* + * if (cnt != nspCounts[depth]) { + * writer.write(' '); + * writer.write("xmlns"); + * if (nspStack[cnt * 2] != null) { + * writer.write(':'); + * writer.write(nspStack[cnt * 2]); + * } + * writer.write("=\""); + * writeEscaped(nspStack[cnt * 2 + 1], '"'); + * writer.write('"'); + * } + */ + + writer.write(' '); + if (!StringUtils.isEmpty(prefix)) { + writer.write(prefix); + writer.write(':'); + } + writer.write(name); + writer.write('='); + char q = value.indexOf('"') == -1 ? '"' : '\''; + writer.write(q); + writeEscaped(value, q); + writer.write(q); + + return this; + } + + public void flush() throws IOException { + check(false); + writer.flush(); + } + + /* + * public void close() throws IOException { + * check(); + * writer.close(); + * } + */ + public XmlSerializer endTag(String namespace, String name) + throws IOException { + + if (!pending) + { + depth--; + // if (namespace == null) + // namespace = ""; + } + + if ((namespace == null + && elementStack[depth * 3] != null) + || (namespace != null + && !namespace.equals(elementStack[depth * 3])) + || !elementStack[depth * 3 + 2].equals(name)) { + throw new IllegalArgumentException("</{"+namespace+"}"+name+"> does not match start"); + } + + if (pending) { + check(true); + depth--; + } + else { + if (indent[depth + 1]) { + writer.write("\r\n"); + for (int i = 0; i < depth; i++) { + writer.write(" "); + } + } + + writer.write("</"); + String prefix = elementStack[depth * 3 + 1]; + if (!StringUtils.isEmpty(prefix)) { + writer.write(prefix); + writer.write(':'); + } + writer.write(name); + writer.write('>'); + } + + nspCounts[depth + 1] = nspCounts[depth]; + return this; + } + + public String getNamespace() { + return getDepth() == 0 ? null : elementStack[getDepth() * 3 - 3]; + } + + public String getName() { + return getDepth() == 0 ? null : elementStack[getDepth() * 3 - 1]; + } + + public int getDepth() { + return pending ? depth + 1 : depth; + } + + public XmlSerializer text(String text) throws IOException { + check(false); + indent[depth] = false; + writeEscaped(text, -1); + return this; + } + + public XmlSerializer text(char[] text, int start, int len) + throws IOException { + text(new String(text, start, len)); + return this; + } + + public void cdsect(String data) throws IOException { + check(false); + // BEGIN android-changed: ]]> is not allowed within a CDATA, + // so break and start a new one when necessary. + data = data.replace("]]>", "]]]]><![CDATA[>"); + char[] chars = data.toCharArray(); + // We also aren't allowed any invalid characters. + for (char ch : chars) { + boolean valid = (ch >= 0x20 && ch <= 0xd7ff) || + (ch == '\t' || ch == '\n' || ch == '\r') || + (ch >= 0xe000 && ch <= 0xfffd); + if (!valid) { + reportInvalidCharacter(ch); + } + } + writer.write("<![CDATA["); + writer.write(chars, 0, chars.length); + writer.write("]]>"); + // END android-changed + } + + public void comment(String comment) throws IOException { + check(false); + writer.write("<!--"); + writer.write(comment); + writer.write("-->"); + } + + public void processingInstruction(String pi) + throws IOException { + check(false); + writer.write("<?"); + writer.write(pi); + writer.write("?>"); + } +}
\ No newline at end of file diff --git a/main/thirdparty/com/viewpagerindicator/PageIndicator.java b/main/thirdparty/com/viewpagerindicator/PageIndicator.java new file mode 100644 index 0000000..26414d8 --- /dev/null +++ b/main/thirdparty/com/viewpagerindicator/PageIndicator.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2011 Patrik Akerfeldt + * Copyright (C) 2011 Jake Wharton + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.viewpagerindicator; + +import android.support.v4.view.ViewPager; + +/** + * A PageIndicator is responsible to show an visual indicator on the total views + * number and the current visible view. + */ +public interface PageIndicator extends ViewPager.OnPageChangeListener { + /** + * Bind the indicator to a ViewPager. + * + * @param view + */ + public void setViewPager(ViewPager view); + + /** + * Bind the indicator to a ViewPager. + * + * @param view + * @param initialPosition + */ + public void setViewPager(ViewPager view, int initialPosition); + + /** + * <p>Set the current page of both the ViewPager and indicator.</p> + * + * <p>This <strong>must</strong> be used if you need to set the page before + * the views are drawn on screen (e.g., default start page).</p> + * + * @param item + */ + public void setCurrentItem(int item); + + /** + * Set a page change listener which will receive forwarded events. + * + * @param listener + */ + public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener); + + /** + * Notify the indicator that the fragment list has changed. + */ + public void notifyDataSetChanged(); +} diff --git a/main/thirdparty/com/viewpagerindicator/TitlePageIndicator.java b/main/thirdparty/com/viewpagerindicator/TitlePageIndicator.java new file mode 100644 index 0000000..94ac962 --- /dev/null +++ b/main/thirdparty/com/viewpagerindicator/TitlePageIndicator.java @@ -0,0 +1,771 @@ +/* + * Copyright (C) 2011 Patrik Akerfeldt + * Copyright (C) 2011 Francisco Figueiredo Jr. + * Copyright (C) 2011 Jake Wharton + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.viewpagerindicator; + +import cgeo.geocaching.R; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewConfigurationCompat; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; + +import java.util.ArrayList; + +/** + * A TitlePageIndicator is a PageIndicator which displays the title of left view + * (if exist), the title of the current select view (centered) and the title of + * the right view (if exist). When the user scrolls the ViewPager then titles are + * also scrolled. + */ +public class TitlePageIndicator extends View implements PageIndicator { + /** + * Percentage indicating what percentage of the screen width away from + * center should the underline be fully faded. A value of 0.25 means that + * halfway between the center of the screen and an edge. + */ + private static final float SELECTION_FADE_PERCENTAGE = 0.25f; + + /** + * Percentage indicating what percentage of the screen width away from + * center should the selected text bold turn off. A value of 0.05 means + * that 10% between the center and an edge. + */ + private static final float BOLD_FADE_PERCENTAGE = 0.05f; + + public enum IndicatorStyle { + None(0), Triangle(1), Underline(2); + + public final int value; + + IndicatorStyle(int value) { + this.value = value; + } + + public static IndicatorStyle fromValue(int value) { + for (IndicatorStyle style : IndicatorStyle.values()) { + if (style.value == value) { + return style; + } + } + return null; + } + } + + private ViewPager mViewPager; + private ViewPager.OnPageChangeListener mListener; + private TitleProvider mTitleProvider; + private int mCurrentPage; + private int mCurrentOffset; + private int mScrollState; + private final Paint mPaintText; + private boolean mBoldText; + private int mColorText; + private int mColorSelected; + private Path mPath = new Path(); + private final Paint mPaintFooterLine; + private IndicatorStyle mFooterIndicatorStyle; + private final Paint mPaintFooterIndicator; + private float mFooterIndicatorHeight; + private float mFooterIndicatorUnderlinePadding; + private float mFooterPadding; + private float mTitlePadding; + private float mTopPadding; + /** Left and right side padding for not active view titles. */ + private float mClipPadding; + private float mFooterLineHeight; + + private static final int INVALID_POINTER = -1; + + private int mTouchSlop; + private float mLastMotionX = -1; + private int mActivePointerId = INVALID_POINTER; + private boolean mIsDragging; + + + public TitlePageIndicator(Context context) { + this(context, null); + } + + public TitlePageIndicator(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.vpiTitlePageIndicatorStyle); + } + + public TitlePageIndicator(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + //Load defaults from resources + final Resources res = getResources(); + final int defaultFooterColor = res.getColor(R.color.default_title_indicator_footer_color); + final float defaultFooterLineHeight = res.getDimension(R.dimen.default_title_indicator_footer_line_height); + final int defaultFooterIndicatorStyle = res.getInteger(R.integer.default_title_indicator_footer_indicator_style); + final float defaultFooterIndicatorHeight = res.getDimension(R.dimen.default_title_indicator_footer_indicator_height); + final float defaultFooterIndicatorUnderlinePadding = res.getDimension(R.dimen.default_title_indicator_footer_indicator_underline_padding); + final float defaultFooterPadding = res.getDimension(R.dimen.default_title_indicator_footer_padding); + final int defaultSelectedColor = res.getColor(R.color.default_title_indicator_selected_color); + final boolean defaultSelectedBold = res.getBoolean(R.bool.default_title_indicator_selected_bold); + final int defaultTextColor = res.getColor(R.color.default_title_indicator_text_color); + final float defaultTextSize = res.getDimension(R.dimen.default_title_indicator_text_size); + final float defaultTitlePadding = res.getDimension(R.dimen.default_title_indicator_title_padding); + final float defaultClipPadding = res.getDimension(R.dimen.default_title_indicator_clip_padding); + final float defaultTopPadding = res.getDimension(R.dimen.default_title_indicator_top_padding); + + //Retrieve styles attributes + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TitlePageIndicator, defStyle, R.style.Widget_TitlePageIndicator); + + //Retrieve the colors to be used for this view and apply them. + mFooterLineHeight = a.getDimension(R.styleable.TitlePageIndicator_footerLineHeight, defaultFooterLineHeight); + mFooterIndicatorStyle = IndicatorStyle.fromValue(a.getInteger(R.styleable.TitlePageIndicator_footerIndicatorStyle, defaultFooterIndicatorStyle)); + mFooterIndicatorHeight = a.getDimension(R.styleable.TitlePageIndicator_footerIndicatorHeight, defaultFooterIndicatorHeight); + mFooterIndicatorUnderlinePadding = a.getDimension(R.styleable.TitlePageIndicator_footerIndicatorUnderlinePadding, defaultFooterIndicatorUnderlinePadding); + mFooterPadding = a.getDimension(R.styleable.TitlePageIndicator_footerPadding, defaultFooterPadding); + mTopPadding = a.getDimension(R.styleable.TitlePageIndicator_topPadding, defaultTopPadding); + mTitlePadding = a.getDimension(R.styleable.TitlePageIndicator_titlePadding, defaultTitlePadding); + mClipPadding = a.getDimension(R.styleable.TitlePageIndicator_clipPadding, defaultClipPadding); + mColorSelected = a.getColor(R.styleable.TitlePageIndicator_selectedColor, defaultSelectedColor); + mColorText = a.getColor(R.styleable.TitlePageIndicator_textColor, defaultTextColor); + mBoldText = a.getBoolean(R.styleable.TitlePageIndicator_selectedBold, defaultSelectedBold); + + final float textSize = a.getDimension(R.styleable.TitlePageIndicator_textSize, defaultTextSize); + final int footerColor = a.getColor(R.styleable.TitlePageIndicator_footerColor, defaultFooterColor); + mPaintText = new Paint(); + mPaintText.setTextSize(textSize); + mPaintText.setAntiAlias(true); + mPaintFooterLine = new Paint(); + mPaintFooterLine.setStyle(Paint.Style.FILL_AND_STROKE); + mPaintFooterLine.setStrokeWidth(mFooterLineHeight); + mPaintFooterLine.setColor(footerColor); + mPaintFooterIndicator = new Paint(); + mPaintFooterIndicator.setStyle(Paint.Style.FILL_AND_STROKE); + mPaintFooterIndicator.setColor(footerColor); + + a.recycle(); + + final ViewConfiguration configuration = ViewConfiguration.get(context); + mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); + } + + + public int getFooterColor() { + return mPaintFooterLine.getColor(); + } + + public void setFooterColor(int footerColor) { + mPaintFooterLine.setColor(footerColor); + mPaintFooterIndicator.setColor(footerColor); + invalidate(); + } + + public float getFooterLineHeight() { + return mFooterLineHeight; + } + + public void setFooterLineHeight(float footerLineHeight) { + mFooterLineHeight = footerLineHeight; + mPaintFooterLine.setStrokeWidth(mFooterLineHeight); + invalidate(); + } + + public float getFooterIndicatorHeight() { + return mFooterIndicatorHeight; + } + + public void setFooterIndicatorHeight(float footerTriangleHeight) { + mFooterIndicatorHeight = footerTriangleHeight; + invalidate(); + } + + public float getFooterIndicatorPadding() { + return mFooterPadding; + } + + public void setFooterIndicatorPadding(float footerIndicatorPadding) { + mFooterPadding = footerIndicatorPadding; + invalidate(); + } + + public IndicatorStyle getFooterIndicatorStyle() { + return mFooterIndicatorStyle; + } + + public void setFooterIndicatorStyle(IndicatorStyle indicatorStyle) { + mFooterIndicatorStyle = indicatorStyle; + invalidate(); + } + + public int getSelectedColor() { + return mColorSelected; + } + + public void setSelectedColor(int selectedColor) { + mColorSelected = selectedColor; + invalidate(); + } + + public boolean isSelectedBold() { + return mBoldText; + } + + public void setSelectedBold(boolean selectedBold) { + mBoldText = selectedBold; + invalidate(); + } + + public int getTextColor() { + return mColorText; + } + + public void setTextColor(int textColor) { + mPaintText.setColor(textColor); + mColorText = textColor; + invalidate(); + } + + public float getTextSize() { + return mPaintText.getTextSize(); + } + + public void setTextSize(float textSize) { + mPaintText.setTextSize(textSize); + invalidate(); + } + + public float getTitlePadding() { + return this.mTitlePadding; + } + + public void setTitlePadding(float titlePadding) { + mTitlePadding = titlePadding; + invalidate(); + } + + public float getTopPadding() { + return this.mTopPadding; + } + + public void setTopPadding(float topPadding) { + mTopPadding = topPadding; + invalidate(); + } + + public float getClipPadding() { + return this.mClipPadding; + } + + public void setClipPadding(float clipPadding) { + mClipPadding = clipPadding; + invalidate(); + } + + /* + * (non-Javadoc) + * + * @see android.view.View#onDraw(android.graphics.Canvas) + */ + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (mViewPager == null) { + return; + } + final int count = mViewPager.getAdapter().getCount(); + if (count == 0) { + return; + } + + //Calculate views bounds + ArrayList<RectF> bounds = calculateAllBounds(mPaintText); + + //Make sure we're on a page that still exists + if (mCurrentPage >= bounds.size()) { + setCurrentItem(bounds.size()-1); + } + + final int countMinusOne = count - 1; + final float halfWidth = getWidth() / 2f; + final int left = getLeft(); + final float leftClip = left + mClipPadding; + final int width = getWidth(); + final int height = getHeight(); + final int right = left + width; + final float rightClip = right - mClipPadding; + + int page = mCurrentPage; + float offsetPercent; + if (mCurrentOffset <= halfWidth) { + offsetPercent = 1.0f * mCurrentOffset / width; + } else { + page += 1; + offsetPercent = 1.0f * (width - mCurrentOffset) / width; + } + final boolean currentSelected = (offsetPercent <= SELECTION_FADE_PERCENTAGE); + final boolean currentBold = (offsetPercent <= BOLD_FADE_PERCENTAGE); + final float selectedPercent = (SELECTION_FADE_PERCENTAGE - offsetPercent) / SELECTION_FADE_PERCENTAGE; + + //Verify if the current view must be clipped to the screen + RectF curPageBound = bounds.get(mCurrentPage); + float curPageWidth = curPageBound.right - curPageBound.left; + if (curPageBound.left < leftClip) { + //Try to clip to the screen (left side) + clipViewOnTheLeft(curPageBound, curPageWidth, left); + } + if (curPageBound.right > rightClip) { + //Try to clip to the screen (right side) + clipViewOnTheRight(curPageBound, curPageWidth, right); + } + + //Left views starting from the current position + if (mCurrentPage > 0) { + for (int i = mCurrentPage - 1; i >= 0; i--) { + RectF bound = bounds.get(i); + //Is left side is outside the screen + if (bound.left < leftClip) { + float w = bound.right - bound.left; + //Try to clip to the screen (left side) + clipViewOnTheLeft(bound, w, left); + //Except if there's an intersection with the right view + RectF rightBound = bounds.get(i + 1); + //Intersection + if (bound.right + mTitlePadding > rightBound.left) { + bound.left = rightBound.left - w - mTitlePadding; + bound.right = bound.left + w; + } + } + } + } + //Right views starting from the current position + if (mCurrentPage < countMinusOne) { + for (int i = mCurrentPage + 1 ; i < count; i++) { + RectF bound = bounds.get(i); + //If right side is outside the screen + if (bound.right > rightClip) { + float w = bound.right - bound.left; + //Try to clip to the screen (right side) + clipViewOnTheRight(bound, w, right); + //Except if there's an intersection with the left view + RectF leftBound = bounds.get(i - 1); + //Intersection + if (bound.left - mTitlePadding < leftBound.right) { + bound.left = leftBound.right + mTitlePadding; + bound.right = bound.left + w; + } + } + } + } + + //Now draw views + for (int i = 0; i < count; i++) { + //Get the title + RectF bound = bounds.get(i); + //Only if one side is visible + if ((bound.left > left && bound.left < right) || (bound.right > left && bound.right < right)) { + final boolean currentPage = (i == page); + //Only set bold if we are within bounds + mPaintText.setFakeBoldText(currentPage && currentBold && mBoldText); + + //Draw text as unselected + mPaintText.setColor(mColorText); + canvas.drawText(mTitleProvider.getTitle(i), bound.left, bound.bottom + mTopPadding, mPaintText); + + //If we are within the selected bounds draw the selected text + if (currentPage && currentSelected) { + mPaintText.setColor(mColorSelected); + mPaintText.setAlpha((int)((mColorSelected >>> 24) * selectedPercent)); + canvas.drawText(mTitleProvider.getTitle(i), bound.left, bound.bottom + mTopPadding, mPaintText); + } + } + } + + //Draw the footer line + mPath.reset(); + mPath.moveTo(0, height - mFooterLineHeight / 2f); + mPath.lineTo(width, height - mFooterLineHeight / 2f); + mPath.close(); + canvas.drawPath(mPath, mPaintFooterLine); + + switch (mFooterIndicatorStyle) { + case Triangle: + mPath.reset(); + mPath.moveTo(halfWidth, height - mFooterLineHeight - mFooterIndicatorHeight); + mPath.lineTo(halfWidth + mFooterIndicatorHeight, height - mFooterLineHeight); + mPath.lineTo(halfWidth - mFooterIndicatorHeight, height - mFooterLineHeight); + mPath.close(); + canvas.drawPath(mPath, mPaintFooterIndicator); + break; + + case Underline: + if (!currentSelected) { + break; + } + + RectF underlineBounds = bounds.get(page); + mPath.reset(); + mPath.moveTo(underlineBounds.left - mFooterIndicatorUnderlinePadding, height - mFooterLineHeight); + mPath.lineTo(underlineBounds.right + mFooterIndicatorUnderlinePadding, height - mFooterLineHeight); + mPath.lineTo(underlineBounds.right + mFooterIndicatorUnderlinePadding, height - mFooterLineHeight - mFooterIndicatorHeight); + mPath.lineTo(underlineBounds.left - mFooterIndicatorUnderlinePadding, height - mFooterLineHeight - mFooterIndicatorHeight); + mPath.close(); + + mPaintFooterIndicator.setAlpha((int)(0xFF * selectedPercent)); + canvas.drawPath(mPath, mPaintFooterIndicator); + mPaintFooterIndicator.setAlpha(0xFF); + break; + + default: + break; + } + } + + @Override + public boolean onTouchEvent(android.view.MotionEvent ev) { + if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) { + return false; + } + + final int action = ev.getAction(); + + switch (action & MotionEventCompat.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + mActivePointerId = MotionEventCompat.getPointerId(ev, 0); + mLastMotionX = ev.getX(); + break; + + case MotionEvent.ACTION_MOVE: { + final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); + final float x = MotionEventCompat.getX(ev, activePointerIndex); + final float deltaX = x - mLastMotionX; + + if (!mIsDragging) { + if (Math.abs(deltaX) > mTouchSlop) { + mIsDragging = true; + } + } + + if (mIsDragging) { + if (!mViewPager.isFakeDragging()) { + mViewPager.beginFakeDrag(); + } + + mLastMotionX = x; + + mViewPager.fakeDragBy(deltaX); + } + + break; + } + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (!mIsDragging) { + final int count = mViewPager.getAdapter().getCount(); + final int width = getWidth(); + final float halfWidth = width / 2f; + final float sixthWidth = width / 6f; + + if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) { + mViewPager.setCurrentItem(mCurrentPage - 1); + return true; + } else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) { + mViewPager.setCurrentItem(mCurrentPage + 1); + return true; + } + } + + mIsDragging = false; + mActivePointerId = INVALID_POINTER; + if (mViewPager.isFakeDragging()) { + mViewPager.endFakeDrag(); + } + break; + + case MotionEventCompat.ACTION_POINTER_DOWN: { + final int index = MotionEventCompat.getActionIndex(ev); + mLastMotionX = MotionEventCompat.getX(ev, index); + mActivePointerId = MotionEventCompat.getPointerId(ev, index); + break; + } + + case MotionEventCompat.ACTION_POINTER_UP: + final int pointerIndex = MotionEventCompat.getActionIndex(ev); + final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); + if (pointerId == mActivePointerId) { + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); + } + mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId)); + break; + } + + return true; + } + + /** + * Set bounds for the right textView including clip padding. + * + * @param curViewBound + * current bounds. + * @param curViewWidth + * width of the view. + */ + private void clipViewOnTheRight(RectF curViewBound, float curViewWidth, int right) { + curViewBound.right = right - mClipPadding; + curViewBound.left = curViewBound.right - curViewWidth; + } + + /** + * Set bounds for the left textView including clip padding. + * + * @param curViewBound + * current bounds. + * @param curViewWidth + * width of the view. + */ + private void clipViewOnTheLeft(RectF curViewBound, float curViewWidth, int left) { + curViewBound.left = left + mClipPadding; + curViewBound.right = mClipPadding + curViewWidth; + } + + /** + * Calculate views bounds and scroll them according to the current index + * + * @param paint + * @param currentIndex + * @return + */ + private ArrayList<RectF> calculateAllBounds(Paint paint) { + ArrayList<RectF> list = new ArrayList<RectF>(); + //For each views (If no values then add a fake one) + final int count = mViewPager.getAdapter().getCount(); + final int width = getWidth(); + final int halfWidth = width / 2; + for (int i = 0; i < count; i++) { + RectF bounds = calcBounds(i, paint); + float w = (bounds.right - bounds.left); + float h = (bounds.bottom - bounds.top); + bounds.left = (halfWidth) - (w / 2) - mCurrentOffset + ((i - mCurrentPage) * width); + bounds.right = bounds.left + w; + bounds.top = 0; + bounds.bottom = h; + list.add(bounds); + } + + return list; + } + + /** + * Calculate the bounds for a view's title + * + * @param index + * @param paint + * @return + */ + private RectF calcBounds(int index, Paint paint) { + //Calculate the text bounds + RectF bounds = new RectF(); + bounds.right = paint.measureText(mTitleProvider.getTitle(index)); + bounds.bottom = paint.descent() - paint.ascent(); + return bounds; + } + + @Override + public void setViewPager(ViewPager view) { + final PagerAdapter adapter = view.getAdapter(); + if (adapter == null) { + throw new IllegalStateException("ViewPager does not have adapter instance."); + } + if (!(adapter instanceof TitleProvider)) { + throw new IllegalStateException("ViewPager adapter must implement TitleProvider to be used with TitlePageIndicator."); + } + mViewPager = view; + mViewPager.setOnPageChangeListener(this); + mTitleProvider = (TitleProvider)adapter; + invalidate(); + } + + @Override + public void setViewPager(ViewPager view, int initialPosition) { + setViewPager(view); + setCurrentItem(initialPosition); + } + + @Override + public void notifyDataSetChanged() { + invalidate(); + } + + @Override + public void setCurrentItem(int item) { + if (mViewPager == null) { + throw new IllegalStateException("ViewPager has not been bound."); + } + mViewPager.setCurrentItem(item); + mCurrentPage = item; + invalidate(); + } + + @Override + public void onPageScrollStateChanged(int state) { + mScrollState = state; + + if (mListener != null) { + mListener.onPageScrollStateChanged(state); + } + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + mCurrentPage = position; + mCurrentOffset = positionOffsetPixels; + invalidate(); + + if (mListener != null) { + mListener.onPageScrolled(position, positionOffset, positionOffsetPixels); + } + } + + @Override + public void onPageSelected(int position) { + if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { + mCurrentPage = position; + invalidate(); + } + + if (mListener != null) { + mListener.onPageSelected(position); + } + } + + @Override + public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { + mListener = listener; + } + + /* + * (non-Javadoc) + * + * @see android.view.View#onMeasure(int, int) + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); + } + + /** + * Determines the width of this view + * + * @param measureSpec + * A measureSpec packed into an int + * @return The width of the view, honoring constraints from measureSpec + */ + private int measureWidth(int measureSpec) { + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode != MeasureSpec.EXACTLY) { + throw new IllegalStateException(getClass().getSimpleName() + " can only be used in EXACTLY mode."); + } + return specSize; + } + + /** + * Determines the height of this view + * + * @param measureSpec + * A measureSpec packed into an int + * @return The height of the view, honoring constraints from measureSpec + */ + private int measureHeight(int measureSpec) { + float result; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + //We were told how big to be + result = specSize; + } else { + //Calculate the text bounds + RectF bounds = new RectF(); + bounds.bottom = mPaintText.descent()-mPaintText.ascent(); + result = bounds.bottom - bounds.top + mFooterLineHeight + mFooterPadding + mTopPadding; + if (mFooterIndicatorStyle != IndicatorStyle.None) { + result += mFooterIndicatorHeight; + } + } + return (int)result; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState savedState = (SavedState)state; + super.onRestoreInstanceState(savedState.getSuperState()); + mCurrentPage = savedState.currentPage; + requestLayout(); + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState savedState = new SavedState(superState); + savedState.currentPage = mCurrentPage; + return savedState; + } + + static class SavedState extends BaseSavedState { + int currentPage; + + public SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + currentPage = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(currentPage); + } + + public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { + @Override + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} diff --git a/main/thirdparty/com/viewpagerindicator/TitleProvider.java b/main/thirdparty/com/viewpagerindicator/TitleProvider.java new file mode 100644 index 0000000..2a04b65 --- /dev/null +++ b/main/thirdparty/com/viewpagerindicator/TitleProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2011 Patrik Akerfeldt + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.viewpagerindicator; + +/** + * A TitleProvider provides the title to display according to a view. + */ +public interface TitleProvider { + /** + * Returns the title of the view at position + * @param position + * @return + */ + public String getTitle(int position); +} diff --git a/main/thirdparty/gnu/android/app/appmanualclient/AppManualReaderClient.java b/main/thirdparty/gnu/android/app/appmanualclient/AppManualReaderClient.java new file mode 100644 index 0000000..af4c03e --- /dev/null +++ b/main/thirdparty/gnu/android/app/appmanualclient/AppManualReaderClient.java @@ -0,0 +1,375 @@ +package gnu.android.app.appmanualclient; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.util.Log; + +import java.util.List; + +/** + * The "App Manual Reader" client is a class to be used in applications which + * want to offer their users manuals through the gnu.android.appmanualreader + * application. Such applications do not need to include the whole + * "App Manual Reader" app but instead just have to include only this little + * package. This package then provides the mechanism to open suitable installed + * manuals. It does not include any manuals itself. + * <p> + * + * (c) 2011 Geocrasher (geocrasher@gmx.eu) + * <p> + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * <p> + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * <p> + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + * @author Geocrasher + */ +public class AppManualReaderClient { + + /** + * The URI scheme used to identify application manual URIs when flinging + * Intents around within an Android device, in the hope that there are + * activities registered which will handle such application manual URIs. + * Usually, there won't be just a single activity registered but instead + * many, depending on how many manuals are installed on an Android device. + */ + public static final String URI_SCHEME_APPMANUAL = "appmanual"; + + /** + * Standardized topic for opening a manual at its beginning. + * + * @see #openManual(String, String, Context) + * @see #openManual(String, String, Context, String) + */ + public static final String TOPIC_HOME = "andtw-home"; + /** + * Standardized topic for opening the index of a manual. + * + * @see #openManual(String, String, Context) + * @see #openManual(String, String, Context, String) + */ + public static final String TOPIC_INDEX = "andtw-index"; + /** + * Standardized topic for opening a manual's "about" topic. + * + * @see #openManual(String, String, Context) + * @see #openManual(String, String, Context, String) + */ + public static final String TOPIC_ABOUT_MANUAL = "andtw-about"; + + /** + * Convenience function to open a manual at a specific topic. See + * {@link #openManual(String, String, Context, String)} for a detailed + * description. + * + * @param manualIdentifier + * the identifier of the manual to open. This identifier must + * uniquely identify the manual as such, independent of the + * particular locale the manual is intended for. + * @param topic + * the topic to open. Please do not use spaces for topic names. + * With respect to the TiddlyWiki infrastructure used for manuals + * the topic needs to the tag of a (single) tiddler. This way + * manuals can be localized (especially their topic titles) + * without breaking an app's knowledge about topics. Some + * standardized topics are predefined, such as {@link #TOPIC_HOME}, {@link #TOPIC_INDEX}, and + * {@link #TOPIC_ABOUT_MANUAL}. + * @param context + * the context (usually an Activity) from which the manual is to + * be opened. In particular, this context is required to derive + * the proper current locale configuration in order to open + * appropriate localized manuals, if installed. + * + * @exception ActivityNotFoundException + * there is no suitable manual installed and all combinations + * of locale scope failed to activate any manual. + * + * @see #openManual(String, String, Context, String, boolean) + */ + public static void openManual(String manualIdentifier, String topic, + Context context) throws ActivityNotFoundException { + openManual(manualIdentifier, topic, context, null, false); + } + + /** + * Convenience function to open a manual at a specific topic. See + * {@link #openManual(String, String, Context, String)} for a detailed + * description. + * + * @param manualIdentifier + * the identifier of the manual to open. This identifier must + * uniquely identify the manual as such, independent of the + * particular locale the manual is intended for. + * @param topic + * the topic to open. Please do not use spaces for topic names. + * With respect to the TiddlyWiki infrastructure used for manuals + * the topic needs to the tag of a (single) tiddler. This way + * manuals can be localized (especially their topic titles) + * without breaking an app's knowledge about topics. Some + * standardized topics are predefined, such as {@link #TOPIC_HOME}, {@link #TOPIC_INDEX}, and + * {@link #TOPIC_ABOUT_MANUAL}. + * @param context + * the context (usually an Activity) from which the manual is to + * be opened. In particular, this context is required to derive + * the proper current locale configuration in order to open + * appropriate localized manuals, if installed. + * @param fallbackUri + * either <code>null</code> or a fallback URI to be used in case + * the user has not installed any suitable manual. + * + * @exception ActivityNotFoundException + * there is no suitable manual installed and all combinations + * of locale scope failed to activate any manual. + * + * @see #openManual(String, String, Context, String, boolean) + */ + public static void openManual(String manualIdentifier, String topic, + Context context, String fallbackUri) + throws ActivityNotFoundException { + openManual(manualIdentifier, topic, context, fallbackUri, false); + } + + /** + * Opens a manual at a specific topic. At least it tries to open a manual. + * As manuals are (usually) installed separately and we use late binding in + * form of implicit intents, a lot of things can go wrong. + * + * We use late binding and the intent architecture in particular as follows: + * first, we use our own URI scheme called "appmanual". Second, we use the + * host field as a unique manual identifier (such as "c-geo" for the app + * manuals for a map which must not be named by the powers that wanna be). + * Third, a localized manual is differentiated as a path with a single + * element in form of (in this precedence) "/lang_country_variant", + * "/lang__variant", "/lang_country", "/lang", or "/". Fourth, the topic to + * open is encoded as the a fragment "#topic=mytopic". + * + * In order to support localization, manuals can register themselves with + * different URIs. + * + * @param manualIdentifier + * the identifier of the manual to open. This identifier must + * uniquely identify the manual as such, independent of the + * particular locale the manual is intended for. + * @param topic + * the topic to open. Please do not use spaces for topic names. + * With respect to the TiddlyWiki infrastructure used for manuals + * the topic needs to the tag of a (single) tiddler. This way + * manuals can be localized (especially their topic titles) + * without breaking an app's knowledge about topics. Some + * standardized topics are predefined, such as + * {@link #TOPIC_HOME}, {@link #TOPIC_INDEX}, and + * {@link #TOPIC_ABOUT_MANUAL}. + * @param context + * the context (usually an Activity) from which the manual is to + * be opened. In particular, this context is required to derive + * the proper current locale configuration in order to open + * appropriate localized manuals, if installed. + * @param fallbackUri + * either <code>null</code> or a fallback URI to be used in case + * the user has not installed any suitable manual. + * @param contextAffinity + * if <code>true</code>, then we try to open the manual within + * the context, if possible. That is, if the package of the + * calling context also offers suitable activity registrations, + * then we will prefer them over any other registrations. If you + * don't know what this means, then you probably don't need this + * very special capability and should specify <code>false</code> + * for this parameter. + * + * @exception ActivityNotFoundException + * there is no suitable manual installed and all combinations + * of locale scope failed to activate any manual and no + * {@literal fallbackUri} was given. + */ + public static void openManual(String manualIdentifier, String topic, + Context context, String fallbackUri, boolean contextAffinity) + throws ActivityNotFoundException { + // + // The path of an "appmanual:" URI consists simply of the locale + // information. This allows manual packages to register themselves + // for both very specific locales as well as very broad ones. + // + String localePath = "/" + + context.getResources().getConfiguration().locale.toString(); + // + // We later need this intent in order to try to launch an appropriate + // manual (respectively its manual viewer). And yes, we need to set + // the intent's category explicitly, even as we will later use + // startActivity(): if we don't do this, the proper activity won't be + // started albeit the filter almost matches. That dirty behavior (it is + // documented wrong) had cost me half a day until I noticed some + // informational log entry generated from the ActivityManager. Grrrr! + // + Intent intent = new Intent(Intent.ACTION_VIEW); + int defaultIntentFlags = intent.getFlags(); + intent.addCategory(Intent.CATEGORY_DEFAULT); + // + // Try to open the manual in the following order (subject to + // availability): + // 1. manualIdentifier_lang_country_variant (can also be + // manualIdentifier_lang__variant in some cases) + // 2. manualIdentifier_lang_country + // 3. manualIdentifier_lang + // 4. manualIdentifier + // Of course, manuals are free to register more than one Intent, + // in particular, the should register also the plain manualIdentifier + // as a suitable fallback strategy. Even when installing multiple + // manuals this doesn't matter, as the user then can choose which + // one to use on a single or permanent basis. + // + while (true) { + Uri uri = Uri.parse(URI_SCHEME_APPMANUAL + "://" + manualIdentifier + + localePath + "#topic='" + topic + "'"); + // Note: we do not use a MIME type for this. + intent.setData(uri); + intent.setFlags(defaultIntentFlags); + if ( contextAffinity ) { + // + // What is happening here? Well, here we try something that we + // would like to call "package affinity activity resolving". + // Given an implicit(!) intent we try to figure out whether the + // package of the context which is trying to open the manual is + // able to resolve the intent. If this is the case, then we + // simply turn the implicit intent into an explicit(!) intent. + // We do this by setting the concrete module, that is: package + // name (eventually the one of the calling context) and class + // name within the package. + // + List<ResolveInfo> capableActivities = context + .getPackageManager() + .queryIntentActivityOptions(null, null, intent, + PackageManager.MATCH_DEFAULT_ONLY); + int capables = capableActivities.size(); + if ( capables > 1 ) { + for ( int idx = 0; idx < capables; ++idx ) { + ActivityInfo activityInfo = capableActivities.get(idx).activityInfo; + if ( activityInfo.packageName.contentEquals(context + .getPackageName()) ) { + intent.setClassName(activityInfo.packageName, + activityInfo.name); + // + // First match is okay, so we quit searching and + // continue with the usual attempt to start the + // activity. This should not fail, as we already + // found a match; yet the code is very forgiving in + // this respect and would just try another round + // with "downsized" locale requirements. + // + break; + } + } + } + // FIXME + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + } else { + // + // No context affinity required, thus we need to set some flags: + // + // ...NEW_TASK: we want to start the manual reader activity as a + // separate + // task so that it can be kept open, yet in the background when + // returning to the application from which the manual was + // opened. + // + // ...SINGLE_TOP: + // + // ...RESET_TASK_IF_NEEDED: clear the manual reader activities + // down to the root activity. We've set the required + // ...CLEAR_WHEN_TASK_RESET above when opening the meta-manual + // with the context affinity. + // + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_SINGLE_TOP + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + } + try { + String logTag = "appmanualclient"; + if ( Log.isLoggable(logTag, Log.INFO) ) { + Log.i(logTag, + "Trying to activate manual: uri=" + uri.toString()); + } + context.startActivity(intent); + // + // We could successfully activate the manual activity, so no + // further trials are required. + // + return; + } catch ( ActivityNotFoundException noActivity ) { + // + // Ensure that we switch back to implicit intent resolving for + // the next round. + // + intent.setComponent(null); + // + // As long as we still have some locale information, reduce it + // and try again a broader locale. + // + if ( localePath.length() > 1 ) { + int underscore = localePath.lastIndexOf('_'); + if ( underscore > 0 ) { + localePath = localePath.substring(0, underscore); + // + // Handle the case where we have a locale variant, yet + // no locale country, thus two underscores in immediate + // series. Get rid of both. + // + if ( localePath.endsWith("_") ) { + localePath = localePath + .substring(0, underscore - 1); + } + } else { + // + // Ready for the last round: try without any locale + // modifiers. + // + localePath = "/"; + } + } else { + // + // We've tried all combinations, so we've run out of them + // and bail out. + // + break; + } + } + // + // Okay, go for the next round, we've updated (or rather trimmed) + // the localeIdent, so let us try this. + // + } + // + // If we reach this code point then no suitable activity could be found + // and activated. In case the caller specified a fallback URI we will + // try to open that. As this will activate a suitable browser and this + // is an asynchronous activity we won't get back any negative results, + // such as 404's. Here we will only see such problems that prevented the + // start of a suitable browsing activity. + // + if ( fallbackUri != null ) { + intent = new Intent(Intent.ACTION_VIEW, Uri.parse(fallbackUri)); + intent.addCategory(Intent.CATEGORY_BROWSABLE); + context.startActivity(intent); + } + // + // We could not activate any manual and there was no fallback URI to + // open, so we finally bail out unsuccessful with an exception. + // + throw new ActivityNotFoundException(); + } +} diff --git a/main/thirdparty/org/openintents/intents/FileManagerIntents.java b/main/thirdparty/org/openintents/intents/FileManagerIntents.java new file mode 100644 index 0000000..8ff10c8 --- /dev/null +++ b/main/thirdparty/org/openintents/intents/FileManagerIntents.java @@ -0,0 +1,127 @@ +/*
+ * Copyright (C) 2008 OpenIntents.org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openintents.intents;
+
+/**
+ * Provides OpenIntents actions, extras, and categories used by providers.
+ * <p>These specifiers extend the standard Android specifiers.</p>
+ */
+public final class FileManagerIntents {
+
+ /**
+ * Activity Action: Pick a file through the file manager, or let user
+ * specify a custom file name.
+ * Data is the current file name or file name suggestion.
+ * Returns a new file name as file URI in data.
+ *
+ * <p>Constant Value: "org.openintents.action.PICK_FILE"</p>
+ */
+ public static final String ACTION_PICK_FILE = "org.openintents.action.PICK_FILE";
+
+ /**
+ * Activity Action: Pick a directory through the file manager, or let user
+ * specify a custom file name.
+ * Data is the current directory name or directory name suggestion.
+ * Returns a new directory name as file URI in data.
+ *
+ * <p>Constant Value: "org.openintents.action.PICK_DIRECTORY"</p>
+ */
+ public static final String ACTION_PICK_DIRECTORY = "org.openintents.action.PICK_DIRECTORY";
+
+ /**
+ * Activity Action: Move, copy or delete after select entries.
+ * Data is the current directory name or directory name suggestion.
+ *
+ * <p>Constant Value: "org.openintents.action.MULTI_SELECT"</p>
+ */
+ public static final String ACTION_MULTI_SELECT = "org.openintents.action.MULTI_SELECT";
+
+ public static final String ACTION_SEARCH_STARTED = "org.openintents.action.SEARCH_STARTED";
+
+ public static final String ACTION_SEARCH_FINISHED = "org.openintens.action.SEARCH_FINISHED";
+
+ /**
+ * The title to display.
+ *
+ * <p>This is shown in the title bar of the file manager.</p>
+ *
+ * <p>Constant Value: "org.openintents.extra.TITLE"</p>
+ */
+ public static final String EXTRA_TITLE = "org.openintents.extra.TITLE";
+
+ /**
+ * The text on the button to display.
+ *
+ * <p>Depending on the use, it makes sense to set this to "Open" or "Save".</p>
+ *
+ * <p>Constant Value: "org.openintents.extra.BUTTON_TEXT"</p>
+ */
+ public static final String EXTRA_BUTTON_TEXT = "org.openintents.extra.BUTTON_TEXT";
+
+ /**
+ * Flag indicating to show only writeable files and folders.
+ *
+ * <p>Constant Value: "org.openintents.extra.WRITEABLE_ONLY"</p>
+ */
+ public static final String EXTRA_WRITEABLE_ONLY = "org.openintents.extra.WRITEABLE_ONLY";
+
+ /**
+ * The path to prioritize in search. Usually denotes the path the user was on when the search was initiated.
+ *
+ * <p>Constant Value: "org.openintents.extra.SEARCH_INIT_PATH"</p>
+ */
+ public static final String EXTRA_SEARCH_INIT_PATH = "org.openintents.extra.SEARCH_INIT_PATH";
+
+ /**
+ * The search query as sent to SearchService.
+ *
+ * <p>Constant Value: "org.openintents.extra.SEARCH_QUERY"</p>
+ */
+ public static final String EXTRA_SEARCH_QUERY = "org.openintents.extra.SEARCH_QUERY";
+
+ /**
+ * <p>Constant Value: "org.openintents.extra.DIR_PATH"</p>
+ */
+ public static final String EXTRA_DIR_PATH = "org.openintents.extra.DIR_PATH";
+
+ /**
+ * Extension by which to filter.
+ *
+ * <p>Constant Value: "org.openintents.extra.FILTER_FILETYPE"</p>
+ */
+ public static final String EXTRA_FILTER_FILETYPE = "org.openintents.extra.FILTER_FILETYPE";
+
+ /**
+ * Mimetype by which to filter.
+ *
+ * <p>Constant Value: "org.openintents.extra.FILTER_MIMETYPE"</p>
+ */
+ public static final String EXTRA_FILTER_MIMETYPE = "org.openintents.extra.FILTER_MIMETYPE";
+
+ /**
+ * Only show directories.
+ *
+ * <p>Constant Value: "org.openintents.extra.DIRECTORIES_ONLY"</p>
+ */
+ public static final String EXTRA_DIRECTORIES_ONLY = "org.openintents.extra.DIRECTORIES_ONLY";
+
+ public static final String EXTRA_DIALOG_FILE_HOLDER = "org.openintents.extra.DIALOG_FILE";
+
+ public static final String EXTRA_IS_GET_CONTENT_INITIATED = "org.openintents.extra.ENABLE_ACTIONS";
+
+ public static final String EXTRA_FILENAME = "org.openintents.extra.FILENAME";
+}
|
