diff --git a/app/build.gradle b/app/build.gradle index 4a9dd065..debea86e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,7 +16,7 @@ android { buildTypes { release { - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } } @@ -27,5 +27,6 @@ android { } dependencies { - compileOnly 'com.android.support:support-annotations:27.0.2' + compile 'com.android.support:support-core-ui:27.1.1' + compileOnly 'com.android.support:support-annotations:27.1.1' } diff --git a/app/src/main/java/android/support/v4/content/FileProvider.java b/app/src/main/java/android/support/v4/content/FileProvider.java deleted file mode 100644 index 12f7a5b1..00000000 --- a/app/src/main/java/android/support/v4/content/FileProvider.java +++ /dev/null @@ -1,774 +0,0 @@ -/* - * Copyright (C) 2013 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.content; - -import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; -import static org.xmlpull.v1.XmlPullParser.START_TAG; - -import android.content.ContentProvider; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ProviderInfo; -import android.content.res.XmlResourceParser; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.net.Uri; -import android.os.Environment; -import android.os.ParcelFileDescriptor; -import android.provider.OpenableColumns; -import android.text.TextUtils; -import android.webkit.MimeTypeMap; - -import org.xmlpull.v1.XmlPullParserException; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -/** - * FileProvider is a special subclass of {@link ContentProvider} that facilitates secure sharing - * of files associated with an app by creating a content:// {@link Uri} for a file - * instead of a file:/// {@link Uri}. - *

- * A content URI allows you to grant read and write access using - * temporary access permissions. When you create an {@link Intent} containing - * a content URI, in order to send the content URI - * to a client app, you can also call {@link Intent#setFlags(int) Intent.setFlags()} to add - * permissions. These permissions are available to the client app for as long as the stack for - * a receiving {@link android.app.Activity} is active. For an {@link Intent} going to a - * {@link android.app.Service}, the permissions are available as long as the - * {@link android.app.Service} is running. - *

- * In comparison, to control access to a file:/// {@link Uri} you have to modify the - * file system permissions of the underlying file. The permissions you provide become available to - * any app, and remain in effect until you change them. This level of access is - * fundamentally insecure. - *

- * The increased level of file access security offered by a content URI - * makes FileProvider a key part of Android's security infrastructure. - *

- * This overview of FileProvider includes the following topics: - *

- *
    - *
  1. Defining a FileProvider
  2. - *
  3. Specifying Available Files
  4. - *
  5. Retrieving the Content URI for a File
  6. - *
  7. Granting Temporary Permissions to a URI
  8. - *
  9. Serving a Content URI to Another App
  10. - *
- *

Defining a FileProvider

- *

- * Since the default functionality of FileProvider includes content URI generation for files, you - * don't need to define a subclass in code. Instead, you can include a FileProvider in your app - * by specifying it entirely in XML. To specify the FileProvider component itself, add a - * <provider> - * element to your app manifest. Set the android:name attribute to - * android.support.v4.content.FileProvider. Set the android:authorities - * attribute to a URI authority based on a domain you control; for example, if you control the - * domain mydomain.com you should use the authority - * com.mydomain.fileprovider. Set the android:exported attribute to - * false; the FileProvider does not need to be public. Set the - * android:grantUriPermissions attribute to true, to allow you - * to grant temporary access to files. For example: - *

- *<manifest>
- *    ...
- *    <application>
- *        ...
- *        <provider
- *            android:name="android.support.v4.content.FileProvider"
- *            android:authorities="com.mydomain.fileprovider"
- *            android:exported="false"
- *            android:grantUriPermissions="true">
- *            ...
- *        </provider>
- *        ...
- *    </application>
- *</manifest>
- *

- * If you want to override any of the default behavior of FileProvider methods, extend - * the FileProvider class and use the fully-qualified class name in the android:name - * attribute of the <provider> element. - *

Specifying Available Files

- * A FileProvider can only generate a content URI for files in directories that you specify - * beforehand. To specify a directory, specify the its storage area and path in XML, using child - * elements of the <paths> element. - * For example, the following paths element tells FileProvider that you intend to - * request content URIs for the images/ subdirectory of your private file area. - *
- *<paths xmlns:android="http://schemas.android.com/apk/res/android">
- *    <files-path name="my_images" path="images/"/>
- *    ...
- *</paths>
- *
- *

- * The <paths> element must contain one or more of the following child elements: - *

- *
- *
- *
- *<files-path name="name" path="path" />
- *
- *
- *
- * Represents files in the files/ subdirectory of your app's internal storage - * area. This subdirectory is the same as the value returned by {@link Context#getFilesDir() - * Context.getFilesDir()}. - *
- *
- *<external-path name="name" path="path" />
- *
- *
- *
- * Represents the root of the external storage. The root path of this subdirectory - * is the same that {@link - * Environment#getExternalStorageDirectory() Environment.getExternalStorageDirectory()} - * returns. - *
- *
- *
- *<cache-path name="name" path="path" />
- *
- *
- *
- * Represents files in the cache subdirectory of your app's internal storage area. The root path - * of this subdirectory is the same as the value returned by {@link Context#getCacheDir() - * getCacheDir()}. - *
- *
- *

- * These child elements all use the same attributes: - *

- *
- *
- * name="name" - *
- *
- * A URI path segment. To enforce security, this value hides the name of the subdirectory - * you're sharing. The subdirectory name for this value is contained in the - * path attribute. - *
- *
- * path="path" - *
- *
- * The subdirectory you're sharing. While the name attribute is a URI path - * segment, the path value is an actual subdirectory name. Notice that the - * value refers to a subdirectory, not an individual file or files. You can't - * share a single file by its file name, nor can you specify a subset of files using - * wildcards. - *
- *
- *

- * You must specify a child element of <paths> for each directory that contains - * files for which you want content URIs. For example, these XML elements specify two directories: - *

- *<paths xmlns:android="http://schemas.android.com/apk/res/android">
- *    <files-path name="my_images" path="images/"/>
- *    <files-path name="my_docs" path="docs/"/>
- *</paths>
- *
- *

- * Put the <paths> element and its children in an XML file in your project. - * For example, you can add them to a new file called res/xml/file_paths.xml. - * To link this file to the FileProvider, add a - * <meta-data> element - * as a child of the <provider> element that defines the FileProvider. Set the - * <meta-data> element's "android:name" attribute to - * android.support.FILE_PROVIDER_PATHS. Set the element's "android:resource" attribute - * to @xml/file_paths (notice that you don't specify the .xml - * extension). For example: - *

- *<provider
- *    android:name="android.support.v4.content.FileProvider"
- *    android:authorities="com.mydomain.fileprovider"
- *    android:exported="false"
- *    android:grantUriPermissions="true">
- *    <meta-data
- *        android:name="android.support.FILE_PROVIDER_PATHS"
- *        android:resource="@xml/file_paths" />
- *</provider>
- *
- *

Generating the Content URI for a File

- *

- * To share a file with another app using a content URI, your app has to generate the content URI. - * To generate the content URI, create a new {@link File} for the file, then pass the {@link File} - * to {@link #getUriForFile(Context, String, File) getUriForFile()}. You can send the content URI - * returned by {@link #getUriForFile(Context, String, File) getUriForFile()} to another app in an - * {@link android.content.Intent}. The client app that receives the content URI can open the file - * and access its contents by calling - * {@link android.content.ContentResolver#openFileDescriptor(Uri, String) - * ContentResolver.openFileDescriptor} to get a {@link ParcelFileDescriptor}. - *

- * For example, suppose your app is offering files to other apps with a FileProvider that has the - * authority com.mydomain.fileprovider. To get a content URI for the file - * default_image.jpg in the images/ subdirectory of your internal storage - * add the following code: - *

- *File imagePath = new File(Context.getFilesDir(), "images");
- *File newFile = new File(imagePath, "default_image.jpg");
- *Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
- *
- * As a result of the previous snippet, - * {@link #getUriForFile(Context, String, File) getUriForFile()} returns the content URI - * content://com.mydomain.fileprovider/my_images/default_image.jpg. - *

Granting Temporary Permissions to a URI

- * To grant an access permission to a content URI returned from - * {@link #getUriForFile(Context, String, File) getUriForFile()}, do one of the following: - * - *

Serving a Content URI to Another App

- *

- * There are a variety of ways to serve the content URI for a file to a client app. One common way - * is for the client app to start your app by calling - * {@link android.app.Activity#startActivityForResult(Intent, int, Bundle) startActivityResult()}, - * which sends an {@link Intent} to your app to start an {@link android.app.Activity} in your app. - * In response, your app can immediately return a content URI to the client app or present a user - * interface that allows the user to pick a file. In the latter case, once the user picks the file - * your app can return its content URI. In both cases, your app returns the content URI in an - * {@link Intent} sent via {@link android.app.Activity#setResult(int, Intent) setResult()}. - *

- *

- * You can also put the content URI in a {@link android.content.ClipData} object and then add the - * object to an {@link Intent} you send to a client app. To do this, call - * {@link Intent#setClipData(ClipData) Intent.setClipData()}. When you use this approach, you can - * add multiple {@link android.content.ClipData} objects to the {@link Intent}, each with its own - * content URI. When you call {@link Intent#setFlags(int) Intent.setFlags()} on the {@link Intent} - * to set temporary access permissions, the same permissions are applied to all of the content - * URIs. - *

- *

- * Note: The {@link Intent#setClipData(ClipData) Intent.setClipData()} method is - * only available in platform version 16 (Android 4.1) and later. If you want to maintain - * compatibility with previous versions, you should send one content URI at a time in the - * {@link Intent}. Set the action to {@link Intent#ACTION_SEND} and put the URI in data by calling - * {@link Intent#setData setData()}. - *

- *

More Information

- *

- * To learn more about FileProvider, see the Android training class - * Sharing Files Securely with URIs. - *

- */ -public class FileProvider extends ContentProvider { - private static final String[] COLUMNS = { - OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE }; - - private static final String - META_DATA_FILE_PROVIDER_PATHS = "android.support.FILE_PROVIDER_PATHS"; - - private static final String TAG_ROOT_PATH = "root-path"; - private static final String TAG_FILES_PATH = "files-path"; - private static final String TAG_CACHE_PATH = "cache-path"; - private static final String TAG_EXTERNAL = "external-path"; - - private static final String ATTR_NAME = "name"; - private static final String ATTR_PATH = "path"; - - private static final File DEVICE_ROOT = new File("/"); - - // @GuardedBy("sCache") - private static HashMap sCache = new HashMap(); - - private PathStrategy mStrategy; - - /** - * The default FileProvider implementation does not need to be initialized. If you want to - * override this method, you must provide your own subclass of FileProvider. - */ - @Override - public boolean onCreate() { - return true; - } - - /** - * After the FileProvider is instantiated, this method is called to provide the system with - * information about the provider. - * - * @param context A {@link Context} for the current component. - * @param info A {@link ProviderInfo} for the new provider. - */ - @Override - public void attachInfo(Context context, ProviderInfo info) { - super.attachInfo(context, info); - - // Sanity check our security - if (info.exported) { - throw new SecurityException("Provider must not be exported"); - } - if (!info.grantUriPermissions) { - throw new SecurityException("Provider must grant uri permissions"); - } - - mStrategy = getPathStrategy(context, info.authority); - } - - /** - * Return a content URI for a given {@link File}. Specific temporary - * permissions for the content URI can be set with - * {@link Context#grantUriPermission(String, Uri, int)}, or added - * to an {@link Intent} by calling {@link Intent#setData(Uri) setData()} and then - * {@link Intent#setFlags(int) setFlags()}; in both cases, the applicable flags are - * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and - * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. A FileProvider can only return a - * content {@link Uri} for file paths defined in their <paths> - * meta-data element. See the Class Overview for more information. - * - * @param context A {@link Context} for the current component. - * @param authority The authority of a {@link FileProvider} defined in a - * {@code } element in your app's manifest. - * @param file A {@link File} pointing to the filename for which you want a - * content {@link Uri}. - * @return A content URI for the file. - * @throws IllegalArgumentException When the given {@link File} is outside - * the paths supported by the provider. - */ - public static Uri getUriForFile(Context context, String authority, File file) { - final PathStrategy strategy = getPathStrategy(context, authority); - return strategy.getUriForFile(file); - } - - /** - * Use a content URI returned by - * {@link #getUriForFile(Context, String, File) getUriForFile()} to get information about a file - * managed by the FileProvider. - * FileProvider reports the column names defined in {@link android.provider.OpenableColumns}: - *
    - *
  • {@link android.provider.OpenableColumns#DISPLAY_NAME}
  • - *
  • {@link android.provider.OpenableColumns#SIZE}
  • - *
- * For more information, see - * {@link ContentProvider#query(Uri, String[], String, String[], String) - * ContentProvider.query()}. - * - * @param uri A content URI returned by {@link #getUriForFile}. - * @param projection The list of columns to put into the {@link Cursor}. If null all columns are - * included. - * @param selection Selection criteria to apply. If null then all data that matches the content - * URI is returned. - * @param selectionArgs An array of {@link java.lang.String}, containing arguments to bind to - * the selection parameter. The query method scans selection from left to - * right and iterates through selectionArgs, replacing the current "?" character in - * selection with the value at the current position in selectionArgs. The - * values are bound to selection as {@link java.lang.String} values. - * @param sortOrder A {@link java.lang.String} containing the column name(s) on which to sort - * the resulting {@link Cursor}. - * @return A {@link Cursor} containing the results of the query. - * - */ - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - // ContentProvider has already checked granted permissions - final File file = mStrategy.getFileForUri(uri); - - if (projection == null) { - projection = COLUMNS; - } - - String[] cols = new String[projection.length]; - Object[] values = new Object[projection.length]; - int i = 0; - for (String col : projection) { - if (OpenableColumns.DISPLAY_NAME.equals(col)) { - cols[i] = OpenableColumns.DISPLAY_NAME; - values[i++] = file.getName(); - } else if (OpenableColumns.SIZE.equals(col)) { - cols[i] = OpenableColumns.SIZE; - values[i++] = file.length(); - } - } - - cols = copyOf(cols, i); - values = copyOf(values, i); - - final MatrixCursor cursor = new MatrixCursor(cols, 1); - cursor.addRow(values); - return cursor; - } - - /** - * Returns the MIME type of a content URI returned by - * {@link #getUriForFile(Context, String, File) getUriForFile()}. - * - * @param uri A content URI returned by - * {@link #getUriForFile(Context, String, File) getUriForFile()}. - * @return If the associated file has an extension, the MIME type associated with that - * extension; otherwise application/octet-stream. - */ - @Override - public String getType(Uri uri) { - // ContentProvider has already checked granted permissions - final File file = mStrategy.getFileForUri(uri); - - final int lastDot = file.getName().lastIndexOf('.'); - if (lastDot >= 0) { - final String extension = file.getName().substring(lastDot + 1); - final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - if (mime != null) { - return mime; - } - } - - return "application/octet-stream"; - } - - /** - * By default, this method throws an {@link java.lang.UnsupportedOperationException}. You must - * subclass FileProvider if you want to provide different functionality. - */ - @Override - public Uri insert(Uri uri, ContentValues values) { - throw new UnsupportedOperationException("No external inserts"); - } - - /** - * By default, this method throws an {@link java.lang.UnsupportedOperationException}. You must - * subclass FileProvider if you want to provide different functionality. - */ - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - throw new UnsupportedOperationException("No external updates"); - } - - /** - * Deletes the file associated with the specified content URI, as - * returned by {@link #getUriForFile(Context, String, File) getUriForFile()}. Notice that this - * method does not throw an {@link java.io.IOException}; you must check its return value. - * - * @param uri A content URI for a file, as returned by - * {@link #getUriForFile(Context, String, File) getUriForFile()}. - * @param selection Ignored. Set to {@code null}. - * @param selectionArgs Ignored. Set to {@code null}. - * @return 1 if the delete succeeds; otherwise, 0. - */ - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - // ContentProvider has already checked granted permissions - final File file = mStrategy.getFileForUri(uri); - return file.delete() ? 1 : 0; - } - - /** - * By default, FileProvider automatically returns the - * {@link ParcelFileDescriptor} for a file associated with a content:// - * {@link Uri}. To get the {@link ParcelFileDescriptor}, call - * {@link android.content.ContentResolver#openFileDescriptor(Uri, String) - * ContentResolver.openFileDescriptor}. - * - * To override this method, you must provide your own subclass of FileProvider. - * - * @param uri A content URI associated with a file, as returned by - * {@link #getUriForFile(Context, String, File) getUriForFile()}. - * @param mode Access mode for the file. May be "r" for read-only access, "rw" for read and - * write access, or "rwt" for read and write access that truncates any existing file. - * @return A new {@link ParcelFileDescriptor} with which you can access the file. - */ - @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - // ContentProvider has already checked granted permissions - final File file = mStrategy.getFileForUri(uri); - final int fileMode = modeToMode(mode); - return ParcelFileDescriptor.open(file, fileMode); - } - - /** - * Return {@link PathStrategy} for given authority, either by parsing or - * returning from cache. - */ - private static PathStrategy getPathStrategy(Context context, String authority) { - PathStrategy strat; - synchronized (sCache) { - strat = sCache.get(authority); - if (strat == null) { - try { - strat = parsePathStrategy(context, authority); - } catch (IOException e) { - throw new IllegalArgumentException( - "Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e); - } catch (XmlPullParserException e) { - throw new IllegalArgumentException( - "Failed to parse " + META_DATA_FILE_PROVIDER_PATHS + " meta-data", e); - } - sCache.put(authority, strat); - } - } - return strat; - } - - /** - * Parse and return {@link PathStrategy} for given authority as defined in - * {@link #META_DATA_FILE_PROVIDER_PATHS} {@code }. - * - * @see #getPathStrategy(Context, String) - */ - private static PathStrategy parsePathStrategy(Context context, String authority) - throws IOException, XmlPullParserException { - final SimplePathStrategy strat = new SimplePathStrategy(authority); - - final ProviderInfo info = context.getPackageManager() - .resolveContentProvider(authority, PackageManager.GET_META_DATA); - final XmlResourceParser in = info.loadXmlMetaData( - context.getPackageManager(), META_DATA_FILE_PROVIDER_PATHS); - if (in == null) { - throw new IllegalArgumentException( - "Missing " + META_DATA_FILE_PROVIDER_PATHS + " meta-data"); - } - - int type; - while ((type = in.next()) != END_DOCUMENT) { - if (type == START_TAG) { - final String tag = in.getName(); - - final String name = in.getAttributeValue(null, ATTR_NAME); - String path = in.getAttributeValue(null, ATTR_PATH); - - File target = null; - if (TAG_ROOT_PATH.equals(tag)) { - target = buildPath(DEVICE_ROOT, path); - } else if (TAG_FILES_PATH.equals(tag)) { - target = buildPath(context.getFilesDir(), path); - } else if (TAG_CACHE_PATH.equals(tag)) { - target = buildPath(context.getCacheDir(), path); - } else if (TAG_EXTERNAL.equals(tag)) { - target = buildPath(Environment.getExternalStorageDirectory(), path); - } - - if (target != null) { - strat.addRoot(name, target); - } - } - } - - return strat; - } - - /** - * Strategy for mapping between {@link File} and {@link Uri}. - *

- * Strategies must be symmetric so that mapping a {@link File} to a - * {@link Uri} and then back to a {@link File} points at the original - * target. - *

- * Strategies must remain consistent across app launches, and not rely on - * dynamic state. This ensures that any generated {@link Uri} can still be - * resolved if your process is killed and later restarted. - * - * @see SimplePathStrategy - */ - interface PathStrategy { - /** - * Return a {@link Uri} that represents the given {@link File}. - */ - public Uri getUriForFile(File file); - - /** - * Return a {@link File} that represents the given {@link Uri}. - */ - public File getFileForUri(Uri uri); - } - - /** - * Strategy that provides access to files living under a narrow whitelist of - * filesystem roots. It will throw {@link SecurityException} if callers try - * accessing files outside the configured roots. - *

- * For example, if configured with - * {@code addRoot("myfiles", context.getFilesDir())}, then - * {@code context.getFileStreamPath("foo.txt")} would map to - * {@code content://myauthority/myfiles/foo.txt}. - */ - static class SimplePathStrategy implements PathStrategy { - private final String mAuthority; - private final HashMap mRoots = new HashMap(); - - public SimplePathStrategy(String authority) { - mAuthority = authority; - } - - /** - * Add a mapping from a name to a filesystem root. The provider only offers - * access to files that live under configured roots. - */ - public void addRoot(String name, File root) { - if (TextUtils.isEmpty(name)) { - throw new IllegalArgumentException("Name must not be empty"); - } - - try { - // Resolve to canonical path to keep path checking fast - root = root.getCanonicalFile(); - } catch (IOException e) { - throw new IllegalArgumentException( - "Failed to resolve canonical path for " + root, e); - } - - mRoots.put(name, root); - } - - @Override - public Uri getUriForFile(File file) { - String path; - try { - path = file.getCanonicalPath(); - } catch (IOException e) { - throw new IllegalArgumentException("Failed to resolve canonical path for " + file); - } - - // Find the most-specific root path - Map.Entry mostSpecific = null; - for (Map.Entry root : mRoots.entrySet()) { - final String rootPath = root.getValue().getPath(); - if (path.startsWith(rootPath) && (mostSpecific == null - || rootPath.length() > mostSpecific.getValue().getPath().length())) { - mostSpecific = root; - } - } - - if (mostSpecific == null) { - throw new IllegalArgumentException( - "Failed to find configured root that contains " + path); - } - - // Start at first char of path under root - final String rootPath = mostSpecific.getValue().getPath(); - if (rootPath.endsWith("/")) { - path = path.substring(rootPath.length()); - } else { - path = path.substring(rootPath.length() + 1); - } - - // Encode the tag and path separately - path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/"); - return new Uri.Builder().scheme("content") - .authority(mAuthority).encodedPath(path).build(); - } - - @Override - public File getFileForUri(Uri uri) { - String path = uri.getEncodedPath(); - - final int splitIndex = path.indexOf('/', 1); - final String tag = Uri.decode(path.substring(1, splitIndex)); - path = Uri.decode(path.substring(splitIndex + 1)); - - final File root = mRoots.get(tag); - if (root == null) { - throw new IllegalArgumentException("Unable to find configured root for " + uri); - } - - File file = new File(root, path); - try { - file = file.getCanonicalFile(); - } catch (IOException e) { - throw new IllegalArgumentException("Failed to resolve canonical path for " + file); - } - - if (!file.getPath().startsWith(root.getPath())) { - throw new SecurityException("Resolved path jumped beyond configured root"); - } - - return file; - } - } - - /** - * Copied from ContentResolver.java - */ - private static int modeToMode(String mode) { - int modeBits; - if ("r".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_READ_ONLY; - } else if ("w".equals(mode) || "wt".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY - | ParcelFileDescriptor.MODE_CREATE - | ParcelFileDescriptor.MODE_TRUNCATE; - } else if ("wa".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY - | ParcelFileDescriptor.MODE_CREATE - | ParcelFileDescriptor.MODE_APPEND; - } else if ("rw".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_READ_WRITE - | ParcelFileDescriptor.MODE_CREATE; - } else if ("rwt".equals(mode)) { - modeBits = ParcelFileDescriptor.MODE_READ_WRITE - | ParcelFileDescriptor.MODE_CREATE - | ParcelFileDescriptor.MODE_TRUNCATE; - } else { - throw new IllegalArgumentException("Invalid mode: " + mode); - } - return modeBits; - } - - private static File buildPath(File base, String... segments) { - File cur = base; - for (String segment : segments) { - if (segment != null) { - cur = new File(cur, segment); - } - } - return cur; - } - - private static String[] copyOf(String[] original, int newLength) { - final String[] result = new String[newLength]; - System.arraycopy(original, 0, result, 0, newLength); - return result; - } - - private static Object[] copyOf(Object[] original, int newLength) { - final Object[] result = new Object[newLength]; - System.arraycopy(original, 0, result, 0, newLength); - return result; - } -} diff --git a/app/src/main/java/android/support/v4/view/PagerAdapter.java b/app/src/main/java/android/support/v4/view/PagerAdapter.java deleted file mode 100644 index ef524047..00000000 --- a/app/src/main/java/android/support/v4/view/PagerAdapter.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (C) 2011 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.view; - -import android.database.DataSetObservable; -import android.database.DataSetObserver; -import android.os.Parcelable; -import android.view.View; -import android.view.ViewGroup; - -/** - * Base class providing the adapter to populate pages inside of - * a {@link ViewPager}. You will most likely want to use a more - * specific implementation of this, such as - * {@link android.support.v4.app.FragmentPagerAdapter} or - * {@link android.support.v4.app.FragmentStatePagerAdapter}. - * - *

When you implement a PagerAdapter, you must override the following methods - * at minimum:

- *
    - *
  • {@link #instantiateItem(ViewGroup, int)}
  • - *
  • {@link #destroyItem(ViewGroup, int, Object)}
  • - *
  • {@link #getCount()}
  • - *
  • {@link #isViewFromObject(View, Object)}
  • - *
- * - *

PagerAdapter is more general than the adapters used for - * {@link android.widget.AdapterView AdapterViews}. Instead of providing a - * View recycling mechanism directly ViewPager uses callbacks to indicate the - * steps taken during an update. A PagerAdapter may implement a form of View - * recycling if desired or use a more sophisticated method of managing page - * Views such as Fragment transactions where each page is represented by its - * own Fragment.

- * - *

ViewPager associates each page with a key Object instead of working with - * Views directly. This key is used to track and uniquely identify a given page - * independent of its position in the adapter. A call to the PagerAdapter method - * {@link #startUpdate(ViewGroup)} indicates that the contents of the ViewPager - * are about to change. One or more calls to {@link #instantiateItem(ViewGroup, int)} - * and/or {@link #destroyItem(ViewGroup, int, Object)} will follow, and the end - * of an update will be signaled by a call to {@link #finishUpdate(ViewGroup)}. - * By the time {@link #finishUpdate(ViewGroup) finishUpdate} returns the views - * associated with the key objects returned by - * {@link #instantiateItem(ViewGroup, int) instantiateItem} should be added to - * the parent ViewGroup passed to these methods and the views associated with - * the keys passed to {@link #destroyItem(ViewGroup, int, Object) destroyItem} - * should be removed. The method {@link #isViewFromObject(View, Object)} identifies - * whether a page View is associated with a given key object.

- * - *

A very simple PagerAdapter may choose to use the page Views themselves - * as key objects, returning them from {@link #instantiateItem(ViewGroup, int)} - * after creation and adding them to the parent ViewGroup. A matching - * {@link #destroyItem(ViewGroup, int, Object)} implementation would remove the - * View from the parent ViewGroup and {@link #isViewFromObject(View, Object)} - * could be implemented as return view == object;.

- * - *

PagerAdapter supports data set changes. Data set changes must occur on the - * main thread and must end with a call to {@link #notifyDataSetChanged()} similar - * to AdapterView adapters derived from {@link android.widget.BaseAdapter}. A data - * set change may involve pages being added, removed, or changing position. The - * ViewPager will keep the current page active provided the adapter implements - * the method {@link #getItemPosition(Object)}.

- */ -public abstract class PagerAdapter { - private DataSetObservable mObservable = new DataSetObservable(); - - public static final int POSITION_UNCHANGED = -1; - public static final int POSITION_NONE = -2; - - /** - * Return the number of views available. - */ - public abstract int getCount(); - - /** - * Called when a change in the shown pages is going to start being made. - * @param container The containing View which is displaying this adapter's - * page views. - */ - public void startUpdate(ViewGroup container) { - startUpdate((View) container); - } - - /** - * Create the page for the given position. The adapter is responsible - * for adding the view to the container given here, although it only - * must ensure this is done by the time it returns from - * {@link #finishUpdate(ViewGroup)}. - * - * @param container The containing View in which the page will be shown. - * @param position The page position to be instantiated. - * @return Returns an Object representing the new page. This does not - * need to be a View, but can be some other container of the page. - */ - public Object instantiateItem(ViewGroup container, int position) { - return instantiateItem((View) container, position); - } - - /** - * Remove a page for the given position. The adapter is responsible - * for removing the view from its container, although it only must ensure - * this is done by the time it returns from {@link #finishUpdate(ViewGroup)}. - * - * @param container The containing View from which the page will be removed. - * @param position The page position to be removed. - * @param object The same object that was returned by - * {@link #instantiateItem(View, int)}. - */ - public void destroyItem(ViewGroup container, int position, Object object) { - destroyItem((View) container, position, object); - } - - /** - * Called to inform the adapter of which item is currently considered to - * be the "primary", that is the one show to the user as the current page. - * - * @param container The containing View from which the page will be removed. - * @param position The page position that is now the primary. - * @param object The same object that was returned by - * {@link #instantiateItem(View, int)}. - */ - public void setPrimaryItem(ViewGroup container, int position, Object object) { - setPrimaryItem((View) container, position, object); - } - - /** - * Called when the a change in the shown pages has been completed. At this - * point you must ensure that all of the pages have actually been added or - * removed from the container as appropriate. - * @param container The containing View which is displaying this adapter's - * page views. - */ - public void finishUpdate(ViewGroup container) { - finishUpdate((View) container); - } - - /** - * Called when a change in the shown pages is going to start being made. - * @param container The containing View which is displaying this adapter's - * page views. - * - * @deprecated Use {@link #startUpdate(ViewGroup)} - */ - public void startUpdate(View container) { - } - - /** - * Create the page for the given position. The adapter is responsible - * for adding the view to the container given here, although it only - * must ensure this is done by the time it returns from - * {@link #finishUpdate(ViewGroup)}. - * - * @param container The containing View in which the page will be shown. - * @param position The page position to be instantiated. - * @return Returns an Object representing the new page. This does not - * need to be a View, but can be some other container of the page. - * - * @deprecated Use {@link #instantiateItem(ViewGroup, int)} - */ - public Object instantiateItem(View container, int position) { - throw new UnsupportedOperationException( - "Required method instantiateItem was not overridden"); - } - - /** - * Remove a page for the given position. The adapter is responsible - * for removing the view from its container, although it only must ensure - * this is done by the time it returns from {@link #finishUpdate(View)}. - * - * @param container The containing View from which the page will be removed. - * @param position The page position to be removed. - * @param object The same object that was returned by - * {@link #instantiateItem(View, int)}. - * - * @deprecated Use {@link #destroyItem(ViewGroup, int, Object)} - */ - public void destroyItem(View container, int position, Object object) { - throw new UnsupportedOperationException("Required method destroyItem was not overridden"); - } - - /** - * Called to inform the adapter of which item is currently considered to - * be the "primary", that is the one show to the user as the current page. - * - * @param container The containing View from which the page will be removed. - * @param position The page position that is now the primary. - * @param object The same object that was returned by - * {@link #instantiateItem(View, int)}. - * - * @deprecated Use {@link #setPrimaryItem(ViewGroup, int, Object)} - */ - public void setPrimaryItem(View container, int position, Object object) { - } - - /** - * Called when the a change in the shown pages has been completed. At this - * point you must ensure that all of the pages have actually been added or - * removed from the container as appropriate. - * @param container The containing View which is displaying this adapter's - * page views. - * - * @deprecated Use {@link #finishUpdate(ViewGroup)} - */ - public void finishUpdate(View container) { - } - - /** - * Determines whether a page View is associated with a specific key object - * as returned by {@link #instantiateItem(ViewGroup, int)}. This method is - * required for a PagerAdapter to function properly. - * - * @param view Page View to check for association with object - * @param object Object to check for association with view - * @return true if view is associated with the key object object - */ - public abstract boolean isViewFromObject(View view, Object object); - - /** - * Save any instance state associated with this adapter and its pages that should be - * restored if the current UI state needs to be reconstructed. - * - * @return Saved state for this adapter - */ - public Parcelable saveState() { - return null; - } - - /** - * Restore any instance state associated with this adapter and its pages - * that was previously saved by {@link #saveState()}. - * - * @param state State previously saved by a call to {@link #saveState()} - * @param loader A ClassLoader that should be used to instantiate any restored objects - */ - public void restoreState(Parcelable state, ClassLoader loader) { - } - - /** - * Called when the host view is attempting to determine if an item's position - * has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given - * item has not changed or {@link #POSITION_NONE} if the item is no longer present - * in the adapter. - * - *

The default implementation assumes that items will never - * change position and always returns {@link #POSITION_UNCHANGED}. - * - * @param object Object representing an item, previously returned by a call to - * {@link #instantiateItem(View, int)}. - * @return object's new position index from [0, {@link #getCount()}), - * {@link #POSITION_UNCHANGED} if the object's position has not changed, - * or {@link #POSITION_NONE} if the item is no longer present. - */ - public int getItemPosition(Object object) { - return POSITION_UNCHANGED; - } - - /** - * This method should be called by the application if the data backing this adapter has changed - * and associated views should update. - */ - public void notifyDataSetChanged() { - mObservable.notifyChanged(); - } - - /** - * Register an observer to receive callbacks related to the adapter's data changing. - * - * @param observer The {@link android.database.DataSetObserver} which will receive callbacks. - */ - public void registerDataSetObserver(DataSetObserver observer) { - mObservable.registerObserver(observer); - } - - /** - * Unregister an observer from callbacks related to the adapter's data changing. - * - * @param observer The {@link android.database.DataSetObserver} which will be unregistered. - */ - public void unregisterDataSetObserver(DataSetObserver observer) { - mObservable.unregisterObserver(observer); - } - - /** - * This method may be called by the ViewPager to obtain a title string - * to describe the specified page. This method may return null - * indicating no title for this page. The default implementation returns - * null. - * - * @param position The position of the title requested - * @return A title for the requested page - */ - public CharSequence getPageTitle(int position) { - return null; - } - - /** - * Returns the proportional width of a given page as a percentage of the - * ViewPager's measured width from (0.f-1.f] - * - * @param position The position of the page requested - * @return Proportional width for the given page position - */ - public float getPageWidth(int position) { - return 1.f; - } -} diff --git a/app/src/main/java/android/support/v4/view/ViewPager.java b/app/src/main/java/android/support/v4/view/ViewPager.java deleted file mode 100644 index 1fb488af..00000000 --- a/app/src/main/java/android/support/v4/view/ViewPager.java +++ /dev/null @@ -1,2975 +0,0 @@ -/* - * Copyright (C) 2011 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.view; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.database.DataSetObserver; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.util.Log; -import android.view.FocusFinder; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.SoundEffectConstants; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; -import android.view.animation.Interpolator; -import android.widget.Scroller; - -import android.view.MotionEvent; -import android.widget.EdgeEffect; -import android.view.View.AccessibilityDelegate; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.AccessibilityRecord; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * Layout manager that allows the user to flip left and right - * through pages of data. You supply an implementation of a - * {@link PagerAdapter} to generate the pages that the view shows. - * - *

Note this class is currently under early design and - * development. The API will likely change in later updates of - * the compatibility library, requiring changes to the source code - * of apps when they are compiled against the newer version.

- * - *

ViewPager is most often used in conjunction with {@link android.app.Fragment}, - * which is a convenient way to supply and manage the lifecycle of each page. - * There are standard adapters implemented for using fragments with the ViewPager, - * which cover the most common use cases. These are - * {@link android.support.v4.app.FragmentPagerAdapter} and - * {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these - * classes have simple code showing how to build a full user interface - * with them. - * - *

For more information about how to use ViewPager, read Creating Swipe Views with - * Tabs.

- * - *

Below is a more complicated example of ViewPager, using it in conjunction - * with {@link android.app.ActionBar} tabs. You can find other examples of using - * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code. - * - * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java - * complete} - */ -public class ViewPager extends ViewGroup { - private static final String TAG = "ViewPager"; - private static final boolean DEBUG = false; - - private static final boolean USE_CACHE = false; - - private static final int DEFAULT_OFFSCREEN_PAGES = 1; - private static final int MAX_SETTLE_DURATION = 600; // ms - private static final int MIN_DISTANCE_FOR_FLING = 25; // dips - - private static final int DEFAULT_GUTTER_SIZE = 16; // dips - - private static final int MIN_FLING_VELOCITY = 400; // dips - - private static final int[] LAYOUT_ATTRS = new int[] { - android.R.attr.layout_gravity - }; - - /** - * Used to track what the expected number of items in the adapter should be. - * If the app changes this when we don't expect it, we'll throw a big obnoxious exception. - */ - private int mExpectedAdapterCount; - - static class ItemInfo { - Object object; - int position; - boolean scrolling; - float widthFactor; - float offset; - } - - private static final Comparator COMPARATOR = new Comparator(){ - @Override - public int compare(ItemInfo lhs, ItemInfo rhs) { - return lhs.position - rhs.position; - } - }; - - private static final Interpolator sInterpolator = new Interpolator() { - public float getInterpolation(float t) { - t -= 1.0f; - return t * t * t * t * t + 1.0f; - } - }; - - private final ArrayList mItems = new ArrayList(); - private final ItemInfo mTempItem = new ItemInfo(); - - private final Rect mTempRect = new Rect(); - - private PagerAdapter mAdapter; - private int mCurItem; // Index of currently displayed page. - private int mRestoredCurItem = -1; - private Parcelable mRestoredAdapterState = null; - private ClassLoader mRestoredClassLoader = null; - private Scroller mScroller; - private PagerObserver mObserver; - - private int mPageMargin; - private Drawable mMarginDrawable; - private int mTopPageBounds; - private int mBottomPageBounds; - - // Offsets of the first and last items, if known. - // Set during population, used to determine if we are at the beginning - // or end of the pager data set during touch scrolling. - private float mFirstOffset = -Float.MAX_VALUE; - private float mLastOffset = Float.MAX_VALUE; - - private int mChildWidthMeasureSpec; - private int mChildHeightMeasureSpec; - private boolean mInLayout; - - private boolean mScrollingCacheEnabled; - - private boolean mPopulatePending; - private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES; - - private boolean mIsBeingDragged; - private boolean mIsUnableToDrag; - private int mDefaultGutterSize; - private int mGutterSize; - private int mTouchSlop; - /** - * Position of the last motion event. - */ - private float mLastMotionX; - private float mLastMotionY; - private float mInitialMotionX; - private float mInitialMotionY; - /** - * ID of the active pointer. This is used to retain consistency during - * drags/flings if multiple pointers are used. - */ - private int mActivePointerId = INVALID_POINTER; - /** - * Sentinel value for no current active pointer. - * Used by {@link #mActivePointerId}. - */ - private static final int INVALID_POINTER = -1; - - /** - * Determines speed during touch scrolling - */ - private VelocityTracker mVelocityTracker; - private int mMinimumVelocity; - private int mMaximumVelocity; - private int mFlingDistance; - private int mCloseEnough; - - // If the pager is at least this close to its final position, complete the scroll - // on touch down and let the user interact with the content inside instead of - // "catching" the flinging pager. - private static final int CLOSE_ENOUGH = 2; // dp - - private boolean mFakeDragging; - private long mFakeDragBeginTime; - - private EdgeEffect mLeftEdge; - private EdgeEffect mRightEdge; - - private boolean mFirstLayout = true; - private boolean mNeedCalculatePageOffsets = false; - private boolean mCalledSuper; - private int mDecorChildCount; - - private List mOnPageChangeListeners; - private OnPageChangeListener mOnPageChangeListener; - private OnPageChangeListener mInternalPageChangeListener; - private OnAdapterChangeListener mAdapterChangeListener; - private PageTransformer mPageTransformer; - private Method mSetChildrenDrawingOrderEnabled; - - private static final int DRAW_ORDER_DEFAULT = 0; - private static final int DRAW_ORDER_FORWARD = 1; - private static final int DRAW_ORDER_REVERSE = 2; - private int mDrawingOrder; - private ArrayList mDrawingOrderedChildren; - private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator(); - - /** - * Indicates that the pager is in an idle, settled state. The current page - * is fully in view and no animation is in progress. - */ - public static final int SCROLL_STATE_IDLE = 0; - - /** - * Indicates that the pager is currently being dragged by the user. - */ - public static final int SCROLL_STATE_DRAGGING = 1; - - /** - * Indicates that the pager is in the process of settling to a final position. - */ - public static final int SCROLL_STATE_SETTLING = 2; - - private final Runnable mEndScrollRunnable = new Runnable() { - public void run() { - setScrollState(SCROLL_STATE_IDLE); - populate(); - } - }; - - private int mScrollState = SCROLL_STATE_IDLE; - - /** - * Callback interface for responding to changing state of the selected page. - */ - public interface OnPageChangeListener { - - /** - * This method will be invoked when the current page is scrolled, either as part - * of a programmatically initiated smooth scroll or a user initiated touch scroll. - * - * @param position Position index of the first page currently being displayed. - * Page position+1 will be visible if positionOffset is nonzero. - * @param positionOffset Value from [0, 1) indicating the offset from the page at position. - * @param positionOffsetPixels Value in pixels indicating the offset from position. - */ - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); - - /** - * This method will be invoked when a new page becomes selected. Animation is not - * necessarily complete. - * - * @param position Position index of the new selected page. - */ - public void onPageSelected(int position); - - /** - * Called when the scroll state changes. Useful for discovering when the user - * begins dragging, when the pager is automatically settling to the current page, - * or when it is fully stopped/idle. - * - * @param state The new scroll state. - * @see ViewPager#SCROLL_STATE_IDLE - * @see ViewPager#SCROLL_STATE_DRAGGING - * @see ViewPager#SCROLL_STATE_SETTLING - */ - public void onPageScrollStateChanged(int state); - } - - /** - * Simple implementation of the {@link OnPageChangeListener} interface with stub - * implementations of each method. Extend this if you do not intend to override - * every method of {@link OnPageChangeListener}. - */ - public static class SimpleOnPageChangeListener implements OnPageChangeListener { - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - // This space for rent - } - - @Override - public void onPageSelected(int position) { - // This space for rent - } - - @Override - public void onPageScrollStateChanged(int state) { - // This space for rent - } - } - - /** - * A PageTransformer is invoked whenever a visible/attached page is scrolled. - * This offers an opportunity for the application to apply a custom transformation - * to the page views using animation properties. - * - *

As property animation is only supported as of Android 3.0 and forward, - * setting a PageTransformer on a ViewPager on earlier platform versions will - * be ignored.

- */ - public interface PageTransformer { - /** - * Apply a property transformation to the given page. - * - * @param page Apply the transformation to this page - * @param position Position of page relative to the current front-and-center - * position of the pager. 0 is front and center. 1 is one full - * page position to the right, and -1 is one page position to the left. - */ - public void transformPage(View page, float position); - } - - /** - * Used internally to monitor when adapters are switched. - */ - interface OnAdapterChangeListener { - public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter); - } - - /** - * Used internally to tag special types of child views that should be added as - * pager decorations by default. - */ - interface Decor {} - - public ViewPager(Context context) { - super(context); - initViewPager(); - } - - public ViewPager(Context context, AttributeSet attrs) { - super(context, attrs); - initViewPager(); - } - - void initViewPager() { - setWillNotDraw(false); - setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); - setFocusable(true); - final Context context = getContext(); - mScroller = new Scroller(context, sInterpolator); - final ViewConfiguration configuration = ViewConfiguration.get(context); - final float density = context.getResources().getDisplayMetrics().density; - - mTouchSlop = configuration.getScaledPagingTouchSlop(); - mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density); - mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); - mLeftEdge = new EdgeEffect(context); - mRightEdge = new EdgeEffect(context); - - mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density); - mCloseEnough = (int) (CLOSE_ENOUGH * density); - mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density); - - this.setAccessibilityDelegate(new MyAccessibilityDelegate()); - - if (ViewPagerIcsCompat.getImportantForAccessibility(this) - == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO && Build.VERSION.SDK_INT >= 16) { - this.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - } - - @Override - protected void onDetachedFromWindow() { - removeCallbacks(mEndScrollRunnable); - super.onDetachedFromWindow(); - } - - private void setScrollState(int newState) { - if (mScrollState == newState) { - return; - } - - mScrollState = newState; - if (mPageTransformer != null) { - // PageTransformers can do complex things that benefit from hardware layers. - enableLayers(newState != SCROLL_STATE_IDLE); - } - dispatchOnScrollStateChanged(newState); - } - - /** - * Set a PagerAdapter that will supply views for this pager as needed. - * - * @param adapter Adapter to use - */ - public void setAdapter(PagerAdapter adapter) { - if (mAdapter != null) { - mAdapter.unregisterDataSetObserver(mObserver); - mAdapter.startUpdate(this); - for (int i = 0; i < mItems.size(); i++) { - final ItemInfo ii = mItems.get(i); - mAdapter.destroyItem(this, ii.position, ii.object); - } - mAdapter.finishUpdate(this); - mItems.clear(); - removeNonDecorViews(); - mCurItem = 0; - scrollTo(0, 0); - } - - final PagerAdapter oldAdapter = mAdapter; - mAdapter = adapter; - mExpectedAdapterCount = 0; - - if (mAdapter != null) { - if (mObserver == null) { - mObserver = new PagerObserver(); - } - mAdapter.registerDataSetObserver(mObserver); - mPopulatePending = false; - final boolean wasFirstLayout = mFirstLayout; - mFirstLayout = true; - mExpectedAdapterCount = mAdapter.getCount(); - if (mRestoredCurItem >= 0) { - mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader); - setCurrentItemInternal(mRestoredCurItem, false, true); - mRestoredCurItem = -1; - mRestoredAdapterState = null; - mRestoredClassLoader = null; - } else if (!wasFirstLayout) { - populate(); - } else { - requestLayout(); - } - } - - if (mAdapterChangeListener != null && oldAdapter != adapter) { - mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter); - } - } - - private void removeNonDecorViews() { - for (int i = 0; i < getChildCount(); i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (!lp.isDecor) { - removeViewAt(i); - i--; - } - } - } - - /** - * Retrieve the current adapter supplying pages. - * - * @return The currently registered PagerAdapter - */ - public PagerAdapter getAdapter() { - return mAdapter; - } - - void setOnAdapterChangeListener(OnAdapterChangeListener listener) { - mAdapterChangeListener = listener; - } - - private int getClientWidth() { - return getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); - } - - /** - * Set the currently selected page. If the ViewPager has already been through its first - * layout with its current adapter there will be a smooth animated transition between - * the current item and the specified item. - * - * @param item Item index to select - */ - public void setCurrentItem(int item) { - mPopulatePending = false; - setCurrentItemInternal(item, !mFirstLayout, false); - } - - /** - * Set the currently selected page. - * - * @param item Item index to select - * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately - */ - public void setCurrentItem(int item, boolean smoothScroll) { - mPopulatePending = false; - setCurrentItemInternal(item, smoothScroll, false); - } - - public int getCurrentItem() { - return mCurItem; - } - - void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) { - setCurrentItemInternal(item, smoothScroll, always, 0); - } - - void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { - if (mAdapter == null || mAdapter.getCount() <= 0) { - setScrollingCacheEnabled(false); - return; - } - if (!always && mCurItem == item && mItems.size() != 0) { - setScrollingCacheEnabled(false); - return; - } - - if (item < 0) { - item = 0; - } else if (item >= mAdapter.getCount()) { - item = mAdapter.getCount() - 1; - } - final int pageLimit = mOffscreenPageLimit; - if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) { - // We are doing a jump by more than one page. To avoid - // glitches, we want to keep all current pages in the view - // until the scroll ends. - for (int i=0; iComponents that add a listener should take care to remove it when finished. - * Other components that take ownership of a view may call {@link #clearOnPageChangeListeners()} - * to remove all attached listeners.

- * - * @param listener listener to add - */ - public void addOnPageChangeListener(OnPageChangeListener listener) { - if (mOnPageChangeListeners == null) { - mOnPageChangeListeners = new ArrayList<>(); - } - mOnPageChangeListeners.add(listener); - } - - /** - * Remove a listener that was previously added via - * {@link #addOnPageChangeListener(OnPageChangeListener)}. - * - * @param listener listener to remove - */ - public void removeOnPageChangeListener(OnPageChangeListener listener) { - if (mOnPageChangeListeners != null) { - mOnPageChangeListeners.remove(listener); - } - } - - /** - * Remove all listeners that are notified of any changes in scroll state or position. - */ - public void clearOnPageChangeListeners() { - if (mOnPageChangeListeners != null) { - mOnPageChangeListeners.clear(); - } - } - - /** - * Set a {@link PageTransformer} that will be called for each attached page whenever - * the scroll position is changed. This allows the application to apply custom property - * transformations to each page, overriding the default sliding look and feel. - * - *

Note: Prior to Android 3.0 the property animation APIs did not exist. - * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.

- * - * @param reverseDrawingOrder true if the supplied PageTransformer requires page views - * to be drawn from last to first instead of first to last. - * @param transformer PageTransformer that will modify each page's animation properties - */ - public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) { - if (Build.VERSION.SDK_INT >= 11) { - final boolean hasTransformer = transformer != null; - final boolean needsPopulate = hasTransformer != (mPageTransformer != null); - mPageTransformer = transformer; - setChildrenDrawingOrderEnabledCompat(hasTransformer); - if (hasTransformer) { - mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD; - } else { - mDrawingOrder = DRAW_ORDER_DEFAULT; - } - if (needsPopulate) populate(); - } - } - - void setChildrenDrawingOrderEnabledCompat(boolean enable) { - if (Build.VERSION.SDK_INT >= 7) { - if (mSetChildrenDrawingOrderEnabled == null) { - try { - mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod( - "setChildrenDrawingOrderEnabled", new Class[] { Boolean.TYPE }); - } catch (NoSuchMethodException e) { - Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e); - } - } - try { - mSetChildrenDrawingOrderEnabled.invoke(this, enable); - } catch (Exception e) { - Log.e(TAG, "Error changing children drawing order", e); - } - } - } - - @Override - protected int getChildDrawingOrder(int childCount, int i) { - final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i; - final int result = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex; - return result; - } - - /** - * Set a separate OnPageChangeListener for internal use by the support library. - * - * @param listener Listener to set - * @return The old listener that was set, if any. - */ - OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) { - OnPageChangeListener oldListener = mInternalPageChangeListener; - mInternalPageChangeListener = listener; - return oldListener; - } - - /** - * Returns the number of pages that will be retained to either side of the - * current page in the view hierarchy in an idle state. Defaults to 1. - * - * @return How many pages will be kept offscreen on either side - * @see #setOffscreenPageLimit(int) - */ - public int getOffscreenPageLimit() { - return mOffscreenPageLimit; - } - - /** - * Set the number of pages that should be retained to either side of the - * current page in the view hierarchy in an idle state. Pages beyond this - * limit will be recreated from the adapter when needed. - * - *

This is offered as an optimization. If you know in advance the number - * of pages you will need to support or have lazy-loading mechanisms in place - * on your pages, tweaking this setting can have benefits in perceived smoothness - * of paging animations and interaction. If you have a small number of pages (3-4) - * that you can keep active all at once, less time will be spent in layout for - * newly created view subtrees as the user pages back and forth.

- * - *

You should keep this limit low, especially if your pages have complex layouts. - * This setting defaults to 1.

- * - * @param limit How many pages will be kept offscreen in an idle state. - */ - public void setOffscreenPageLimit(int limit) { - if (limit < DEFAULT_OFFSCREEN_PAGES) { - Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " + - DEFAULT_OFFSCREEN_PAGES); - limit = DEFAULT_OFFSCREEN_PAGES; - } - if (limit != mOffscreenPageLimit) { - mOffscreenPageLimit = limit; - populate(); - } - } - - /** - * Set the margin between pages. - * - * @param marginPixels Distance between adjacent pages in pixels - * @see #getPageMargin() - * @see #setPageMarginDrawable(Drawable) - * @see #setPageMarginDrawable(int) - */ - public void setPageMargin(int marginPixels) { - final int oldMargin = mPageMargin; - mPageMargin = marginPixels; - - final int width = getWidth(); - recomputeScrollPosition(width, width, marginPixels, oldMargin); - - requestLayout(); - } - - /** - * Return the margin between pages. - * - * @return The size of the margin in pixels - */ - public int getPageMargin() { - return mPageMargin; - } - - /** - * Set a drawable that will be used to fill the margin between pages. - * - * @param d Drawable to display between pages - */ - public void setPageMarginDrawable(Drawable d) { - mMarginDrawable = d; - if (d != null) refreshDrawableState(); - setWillNotDraw(d == null); - invalidate(); - } - - /** - * Set a drawable that will be used to fill the margin between pages. - * - * @param resId Resource ID of a drawable to display between pages - */ - public void setPageMarginDrawable(int resId) { - setPageMarginDrawable(getContext().getResources().getDrawable(resId)); - } - - @Override - protected boolean verifyDrawable(Drawable who) { - return super.verifyDrawable(who) || who == mMarginDrawable; - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - final Drawable d = mMarginDrawable; - if (d != null && d.isStateful()) { - d.setState(getDrawableState()); - } - } - - // We want the duration of the page snap animation to be influenced by the distance that - // the screen has to travel, however, we don't want this duration to be effected in a - // purely linear fashion. Instead, we use this method to moderate the effect that the distance - // of travel has on the overall snap duration. - float distanceInfluenceForSnapDuration(float f) { - f -= 0.5f; // center the values about 0. - f *= 0.3f * Math.PI / 2.0f; - return (float) Math.sin(f); - } - - /** - * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. - * - * @param x the number of pixels to scroll by on the X axis - * @param y the number of pixels to scroll by on the Y axis - */ - void smoothScrollTo(int x, int y) { - smoothScrollTo(x, y, 0); - } - - /** - * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. - * - * @param x the number of pixels to scroll by on the X axis - * @param y the number of pixels to scroll by on the Y axis - * @param velocity the velocity associated with a fling, if applicable. (0 otherwise) - */ - void smoothScrollTo(int x, int y, int velocity) { - if (getChildCount() == 0) { - // Nothing to do. - setScrollingCacheEnabled(false); - return; - } - int sx = getScrollX(); - int sy = getScrollY(); - int dx = x - sx; - int dy = y - sy; - if (dx == 0 && dy == 0) { - completeScroll(false); - populate(); - setScrollState(SCROLL_STATE_IDLE); - return; - } - - setScrollingCacheEnabled(true); - setScrollState(SCROLL_STATE_SETTLING); - - final int width = getClientWidth(); - final int halfWidth = width / 2; - final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width); - final float distance = halfWidth + halfWidth * - distanceInfluenceForSnapDuration(distanceRatio); - - int duration = 0; - velocity = Math.abs(velocity); - if (velocity > 0) { - duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); - } else { - final float pageWidth = width * mAdapter.getPageWidth(mCurItem); - final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin); - duration = (int) ((pageDelta + 1) * 100); - } - duration = Math.min(duration, MAX_SETTLE_DURATION); - - mScroller.startScroll(sx, sy, dx, dy, duration); - ViewPagerIcsCompat.postInvalidateOnAnimation(this); - } - - ItemInfo addNewItem(int position, int index) { - ItemInfo ii = new ItemInfo(); - ii.position = position; - ii.object = mAdapter.instantiateItem(this, position); - ii.widthFactor = mAdapter.getPageWidth(position); - if (index < 0 || index >= mItems.size()) { - mItems.add(ii); - } else { - mItems.add(index, ii); - } - return ii; - } - - void dataSetChanged() { - // This method only gets called if our observer is attached, so mAdapter is non-null. - - final int adapterCount = mAdapter.getCount(); - mExpectedAdapterCount = adapterCount; - boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 && - mItems.size() < adapterCount; - int newCurrItem = mCurItem; - - boolean isUpdating = false; - for (int i = 0; i < mItems.size(); i++) { - final ItemInfo ii = mItems.get(i); - final int newPos = mAdapter.getItemPosition(ii.object); - - if (newPos == PagerAdapter.POSITION_UNCHANGED) { - continue; - } - - if (newPos == PagerAdapter.POSITION_NONE) { - mItems.remove(i); - i--; - - if (!isUpdating) { - mAdapter.startUpdate(this); - isUpdating = true; - } - - mAdapter.destroyItem(this, ii.position, ii.object); - needPopulate = true; - - if (mCurItem == ii.position) { - // Keep the current item in the valid range - newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1)); - needPopulate = true; - } - continue; - } - - if (ii.position != newPos) { - if (ii.position == mCurItem) { - // Our current item changed position. Follow it. - newCurrItem = newPos; - } - - ii.position = newPos; - needPopulate = true; - } - } - - if (isUpdating) { - mAdapter.finishUpdate(this); - } - - Collections.sort(mItems, COMPARATOR); - - if (needPopulate) { - // Reset our known page widths; populate will recompute them. - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (!lp.isDecor) { - lp.widthFactor = 0.f; - } - } - - setCurrentItemInternal(newCurrItem, false, true); - requestLayout(); - } - } - - void populate() { - populate(mCurItem); - } - - void populate(int newCurrentItem) { - ItemInfo oldCurInfo = null; - int focusDirection = View.FOCUS_FORWARD; - if (mCurItem != newCurrentItem) { - focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT; - oldCurInfo = infoForPosition(mCurItem); - mCurItem = newCurrentItem; - } - - if (mAdapter == null) { - sortChildDrawingOrder(); - return; - } - - // Bail now if we are waiting to populate. This is to hold off - // on creating views from the time the user releases their finger to - // fling to a new position until we have finished the scroll to - // that position, avoiding glitches from happening at that point. - if (mPopulatePending) { - if (DEBUG) Log.i(TAG, "populate is pending, skipping for now..."); - sortChildDrawingOrder(); - return; - } - - // Also, don't populate until we are attached to a window. This is to - // avoid trying to populate before we have restored our view hierarchy - // state and conflicting with what is restored. - if (getWindowToken() == null) { - return; - } - - mAdapter.startUpdate(this); - - final int pageLimit = mOffscreenPageLimit; - final int startPos = Math.max(0, mCurItem - pageLimit); - final int N = mAdapter.getCount(); - final int endPos = Math.min(N-1, mCurItem + pageLimit); - - if (N != mExpectedAdapterCount) { - String resName; - try { - resName = getResources().getResourceName(getId()); - } catch (Resources.NotFoundException e) { - resName = Integer.toHexString(getId()); - } - throw new IllegalStateException("The application's PagerAdapter changed the adapter's" + - " contents without calling PagerAdapter#notifyDataSetChanged!" + - " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N + - " Pager id: " + resName + - " Pager class: " + getClass() + - " Problematic adapter: " + mAdapter.getClass()); - } - - // Locate the currently focused item or add it if needed. - int curIndex = -1; - ItemInfo curItem = null; - for (curIndex = 0; curIndex < mItems.size(); curIndex++) { - final ItemInfo ii = mItems.get(curIndex); - if (ii.position >= mCurItem) { - if (ii.position == mCurItem) curItem = ii; - break; - } - } - - if (curItem == null && N > 0) { - curItem = addNewItem(mCurItem, curIndex); - } - - // Fill 3x the available width or up to the number of offscreen - // pages requested to either side, whichever is larger. - // If we have no current item we have no work to do. - if (curItem != null) { - float extraWidthLeft = 0.f; - int itemIndex = curIndex - 1; - ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; - final int clientWidth = getClientWidth(); - final float leftWidthNeeded = clientWidth <= 0 ? 0 : - 2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth; - for (int pos = mCurItem - 1; pos >= 0; pos--) { - if (extraWidthLeft >= leftWidthNeeded && pos < startPos) { - if (ii == null) { - break; - } - if (pos == ii.position && !ii.scrolling) { - mItems.remove(itemIndex); - mAdapter.destroyItem(this, pos, ii.object); - if (DEBUG) { - Log.i(TAG, "populate() - destroyItem() with pos: " + pos + - " view: " + ((View) ii.object)); - } - itemIndex--; - curIndex--; - ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; - } - } else if (ii != null && pos == ii.position) { - extraWidthLeft += ii.widthFactor; - itemIndex--; - ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; - } else { - ii = addNewItem(pos, itemIndex + 1); - extraWidthLeft += ii.widthFactor; - curIndex++; - ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; - } - } - - float extraWidthRight = curItem.widthFactor; - itemIndex = curIndex + 1; - if (extraWidthRight < 2.f) { - ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; - final float rightWidthNeeded = clientWidth <= 0 ? 0 : - (float) getPaddingRight() / (float) clientWidth + 2.f; - for (int pos = mCurItem + 1; pos < N; pos++) { - if (extraWidthRight >= rightWidthNeeded && pos > endPos) { - if (ii == null) { - break; - } - if (pos == ii.position && !ii.scrolling) { - mItems.remove(itemIndex); - mAdapter.destroyItem(this, pos, ii.object); - if (DEBUG) { - Log.i(TAG, "populate() - destroyItem() with pos: " + pos + - " view: " + ((View) ii.object)); - } - ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; - } - } else if (ii != null && pos == ii.position) { - extraWidthRight += ii.widthFactor; - itemIndex++; - ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; - } else { - ii = addNewItem(pos, itemIndex); - itemIndex++; - extraWidthRight += ii.widthFactor; - ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; - } - } - } - - calculatePageOffsets(curItem, curIndex, oldCurInfo); - } - - if (DEBUG) { - Log.i(TAG, "Current page list:"); - for (int i=0; i(); - } else { - mDrawingOrderedChildren.clear(); - } - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - mDrawingOrderedChildren.add(child); - } - Collections.sort(mDrawingOrderedChildren, sPositionComparator); - } - } - - private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) { - final int N = mAdapter.getCount(); - final int width = getClientWidth(); - final float marginOffset = width > 0 ? (float) mPageMargin / width : 0; - // Fix up offsets for later layout. - if (oldCurInfo != null) { - final int oldCurPosition = oldCurInfo.position; - // Base offsets off of oldCurInfo. - if (oldCurPosition < curItem.position) { - int itemIndex = 0; - ItemInfo ii = null; - float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset; - for (int pos = oldCurPosition + 1; - pos <= curItem.position && itemIndex < mItems.size(); pos++) { - ii = mItems.get(itemIndex); - while (pos > ii.position && itemIndex < mItems.size() - 1) { - itemIndex++; - ii = mItems.get(itemIndex); - } - while (pos < ii.position) { - // We don't have an item populated for this, - // ask the adapter for an offset. - offset += mAdapter.getPageWidth(pos) + marginOffset; - pos++; - } - ii.offset = offset; - offset += ii.widthFactor + marginOffset; - } - } else if (oldCurPosition > curItem.position) { - int itemIndex = mItems.size() - 1; - ItemInfo ii = null; - float offset = oldCurInfo.offset; - for (int pos = oldCurPosition - 1; - pos >= curItem.position && itemIndex >= 0; pos--) { - ii = mItems.get(itemIndex); - while (pos < ii.position && itemIndex > 0) { - itemIndex--; - ii = mItems.get(itemIndex); - } - while (pos > ii.position) { - // We don't have an item populated for this, - // ask the adapter for an offset. - offset -= mAdapter.getPageWidth(pos) + marginOffset; - pos--; - } - offset -= ii.widthFactor + marginOffset; - ii.offset = offset; - } - } - } - - // Base all offsets off of curItem. - final int itemCount = mItems.size(); - float offset = curItem.offset; - int pos = curItem.position - 1; - mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE; - mLastOffset = curItem.position == N - 1 ? - curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE; - // Previous pages - for (int i = curIndex - 1; i >= 0; i--, pos--) { - final ItemInfo ii = mItems.get(i); - while (pos > ii.position) { - offset -= mAdapter.getPageWidth(pos--) + marginOffset; - } - offset -= ii.widthFactor + marginOffset; - ii.offset = offset; - if (ii.position == 0) mFirstOffset = offset; - } - offset = curItem.offset + curItem.widthFactor + marginOffset; - pos = curItem.position + 1; - // Next pages - for (int i = curIndex + 1; i < itemCount; i++, pos++) { - final ItemInfo ii = mItems.get(i); - while (pos < ii.position) { - offset += mAdapter.getPageWidth(pos++) + marginOffset; - } - if (ii.position == N - 1) { - mLastOffset = offset + ii.widthFactor - 1; - } - ii.offset = offset; - offset += ii.widthFactor + marginOffset; - } - - mNeedCalculatePageOffsets = false; - } - - /** - * This is the persistent state that is saved by ViewPager. Only needed - * if you are creating a sublass of ViewPager that must save its own - * state, in which case it should implement a subclass of this which - * contains that state. - */ - public static class SavedState extends BaseSavedState { - int position; - Parcelable adapterState; - ClassLoader loader; - - public SavedState(Parcelable superState) { - super(superState); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeInt(position); - out.writeParcelable(adapterState, flags); - } - - @Override - public String toString() { - return "FragmentPager.SavedState{" - + Integer.toHexString(System.identityHashCode(this)) - + " position=" + position + "}"; - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - - SavedState(Parcel in) { - super(in); - loader = getClass().getClassLoader(); - position = in.readInt(); - adapterState = in.readParcelable(loader); - this.loader = loader; - } - } - - @Override - public Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - SavedState ss = new SavedState(superState); - ss.position = mCurItem; - if (mAdapter != null) { - ss.adapterState = mAdapter.saveState(); - } - return ss; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - if (!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - - SavedState ss = (SavedState)state; - super.onRestoreInstanceState(ss.getSuperState()); - - if (mAdapter != null) { - mAdapter.restoreState(ss.adapterState, ss.loader); - setCurrentItemInternal(ss.position, false, true); - } else { - mRestoredCurItem = ss.position; - mRestoredAdapterState = ss.adapterState; - mRestoredClassLoader = ss.loader; - } - } - - @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { - if (!checkLayoutParams(params)) { - params = generateLayoutParams(params); - } - final LayoutParams lp = (LayoutParams) params; - lp.isDecor |= child instanceof Decor; - if (mInLayout) { - if (lp != null && lp.isDecor) { - throw new IllegalStateException("Cannot add pager decor view during layout"); - } - lp.needsMeasure = true; - addViewInLayout(child, index, params); - } else { - super.addView(child, index, params); - } - - if (USE_CACHE) { - if (child.getVisibility() != GONE) { - child.setDrawingCacheEnabled(mScrollingCacheEnabled); - } else { - child.setDrawingCacheEnabled(false); - } - } - } - - @Override - public void removeView(View view) { - if (mInLayout) { - removeViewInLayout(view); - } else { - super.removeView(view); - } - } - - ItemInfo infoForChild(View child) { - for (int i=0; i 0 && !mItems.isEmpty()) { - final int widthWithMargin = width - getPaddingLeft() - getPaddingRight() + margin; - final int oldWidthWithMargin = oldWidth - getPaddingLeft() - getPaddingRight() - + oldMargin; - final int xpos = getScrollX(); - final float pageOffset = (float) xpos / oldWidthWithMargin; - final int newOffsetPixels = (int) (pageOffset * widthWithMargin); - - scrollTo(newOffsetPixels, getScrollY()); - if (!mScroller.isFinished()) { - // We now return to your regularly scheduled scroll, already in progress. - final int newDuration = mScroller.getDuration() - mScroller.timePassed(); - ItemInfo targetInfo = infoForPosition(mCurItem); - mScroller.startScroll(newOffsetPixels, 0, - (int) (targetInfo.offset * width), 0, newDuration); - } - } else { - final ItemInfo ii = infoForPosition(mCurItem); - final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0; - final int scrollPos = (int) (scrollOffset * - (width - getPaddingLeft() - getPaddingRight())); - if (scrollPos != getScrollX()) { - completeScroll(false); - scrollTo(scrollPos, getScrollY()); - } - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - final int count = getChildCount(); - int width = r - l; - int height = b - t; - int paddingLeft = getPaddingLeft(); - int paddingTop = getPaddingTop(); - int paddingRight = getPaddingRight(); - int paddingBottom = getPaddingBottom(); - final int scrollX = getScrollX(); - - int decorCount = 0; - - // First pass - decor views. We need to do this in two passes so that - // we have the proper offsets for non-decor views later. - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() != GONE) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - int childLeft = 0; - int childTop = 0; - if (lp.isDecor) { - final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; - final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; - switch (hgrav) { - default: - childLeft = paddingLeft; - break; - case Gravity.LEFT: - childLeft = paddingLeft; - paddingLeft += child.getMeasuredWidth(); - break; - case Gravity.CENTER_HORIZONTAL: - childLeft = Math.max((width - child.getMeasuredWidth()) / 2, - paddingLeft); - break; - case Gravity.RIGHT: - childLeft = width - paddingRight - child.getMeasuredWidth(); - paddingRight += child.getMeasuredWidth(); - break; - } - switch (vgrav) { - default: - childTop = paddingTop; - break; - case Gravity.TOP: - childTop = paddingTop; - paddingTop += child.getMeasuredHeight(); - break; - case Gravity.CENTER_VERTICAL: - childTop = Math.max((height - child.getMeasuredHeight()) / 2, - paddingTop); - break; - case Gravity.BOTTOM: - childTop = height - paddingBottom - child.getMeasuredHeight(); - paddingBottom += child.getMeasuredHeight(); - break; - } - childLeft += scrollX; - child.layout(childLeft, childTop, - childLeft + child.getMeasuredWidth(), - childTop + child.getMeasuredHeight()); - decorCount++; - } - } - } - - final int childWidth = width - paddingLeft - paddingRight; - // Page views. Do this once we have the right padding offsets from above. - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() != GONE) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - ItemInfo ii; - if (!lp.isDecor && (ii = infoForChild(child)) != null) { - int loff = (int) (childWidth * ii.offset); - int childLeft = paddingLeft + loff; - int childTop = paddingTop; - if (lp.needsMeasure) { - // This was added during layout and needs measurement. - // Do it now that we know what we're working with. - lp.needsMeasure = false; - final int widthSpec = MeasureSpec.makeMeasureSpec( - (int) (childWidth * lp.widthFactor), - MeasureSpec.EXACTLY); - final int heightSpec = MeasureSpec.makeMeasureSpec( - (int) (height - paddingTop - paddingBottom), - MeasureSpec.EXACTLY); - child.measure(widthSpec, heightSpec); - } - if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object - + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth() - + "x" + child.getMeasuredHeight()); - child.layout(childLeft, childTop, - childLeft + child.getMeasuredWidth(), - childTop + child.getMeasuredHeight()); - } - } - } - mTopPageBounds = paddingTop; - mBottomPageBounds = height - paddingBottom; - mDecorChildCount = decorCount; - - if (mFirstLayout) { - scrollToItem(mCurItem, false, 0, false); - } - mFirstLayout = false; - } - - @Override - public void computeScroll() { - if (!mScroller.isFinished() && mScroller.computeScrollOffset()) { - int oldX = getScrollX(); - int oldY = getScrollY(); - int x = mScroller.getCurrX(); - int y = mScroller.getCurrY(); - - if (oldX != x || oldY != y) { - scrollTo(x, y); - if (!pageScrolled(x)) { - mScroller.abortAnimation(); - scrollTo(0, y); - } - } - - // Keep on drawing until the animation has finished. - ViewPagerIcsCompat.postInvalidateOnAnimation(this); - return; - } - - // Done with scroll, clean up state. - completeScroll(true); - } - - private boolean pageScrolled(int xpos) { - if (mItems.size() == 0) { - mCalledSuper = false; - onPageScrolled(0, 0, 0); - if (!mCalledSuper) { - throw new IllegalStateException( - "onPageScrolled did not call superclass implementation"); - } - return false; - } - final ItemInfo ii = infoForCurrentScrollPosition(); - final int width = getClientWidth(); - final int widthWithMargin = width + mPageMargin; - final float marginOffset = (float) mPageMargin / width; - final int currentPage = ii.position; - final float pageOffset = (((float) xpos / width) - ii.offset) / - (ii.widthFactor + marginOffset); - final int offsetPixels = (int) (pageOffset * widthWithMargin); - - mCalledSuper = false; - onPageScrolled(currentPage, pageOffset, offsetPixels); - if (!mCalledSuper) { - throw new IllegalStateException( - "onPageScrolled did not call superclass implementation"); - } - return true; - } - - /** - * This method will be invoked when the current page is scrolled, either as part - * of a programmatically initiated smooth scroll or a user initiated touch scroll. - * If you override this method you must call through to the superclass implementation - * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled - * returns. - * - * @param position Position index of the first page currently being displayed. - * Page position+1 will be visible if positionOffset is nonzero. - * @param offset Value from [0, 1) indicating the offset from the page at position. - * @param offsetPixels Value in pixels indicating the offset from position. - */ - protected void onPageScrolled(int position, float offset, int offsetPixels) { - // Offset any decor views if needed - keep them on-screen at all times. - if (mDecorChildCount > 0) { - final int scrollX = getScrollX(); - int paddingLeft = getPaddingLeft(); - int paddingRight = getPaddingRight(); - final int width = getWidth(); - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (!lp.isDecor) continue; - - final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; - int childLeft = 0; - switch (hgrav) { - default: - childLeft = paddingLeft; - break; - case Gravity.LEFT: - childLeft = paddingLeft; - paddingLeft += child.getWidth(); - break; - case Gravity.CENTER_HORIZONTAL: - childLeft = Math.max((width - child.getMeasuredWidth()) / 2, - paddingLeft); - break; - case Gravity.RIGHT: - childLeft = width - paddingRight - child.getMeasuredWidth(); - paddingRight += child.getMeasuredWidth(); - break; - } - childLeft += scrollX; - - final int childOffset = childLeft - child.getLeft(); - if (childOffset != 0) { - child.offsetLeftAndRight(childOffset); - } - } - } - - dispatchOnPageScrolled(position, offset, offsetPixels); - - if (mPageTransformer != null) { - final int scrollX = getScrollX(); - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - if (lp.isDecor) continue; - - final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth(); - mPageTransformer.transformPage(child, transformPos); - } - } - - mCalledSuper = true; - } - - private void dispatchOnPageScrolled(int position, float offset, int offsetPixels) { - if (mOnPageChangeListener != null) { - mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); - } - if (mOnPageChangeListeners != null) { - for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) { - OnPageChangeListener listener = mOnPageChangeListeners.get(i); - if (listener != null) { - listener.onPageScrolled(position, offset, offsetPixels); - } - } - } - if (mInternalPageChangeListener != null) { - mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels); - } - } - - private void dispatchOnPageSelected(int position) { - if (mOnPageChangeListener != null) { - mOnPageChangeListener.onPageSelected(position); - } - if (mOnPageChangeListeners != null) { - for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) { - OnPageChangeListener listener = mOnPageChangeListeners.get(i); - if (listener != null) { - listener.onPageSelected(position); - } - } - } - if (mInternalPageChangeListener != null) { - mInternalPageChangeListener.onPageSelected(position); - } - } - - private void dispatchOnScrollStateChanged(int state) { - if (mOnPageChangeListener != null) { - mOnPageChangeListener.onPageScrollStateChanged(state); - } - if (mOnPageChangeListeners != null) { - for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) { - OnPageChangeListener listener = mOnPageChangeListeners.get(i); - if (listener != null) { - listener.onPageScrollStateChanged(state); - } - } - } - if (mInternalPageChangeListener != null) { - mInternalPageChangeListener.onPageScrollStateChanged(state); - } - } - - private void completeScroll(boolean postEvents) { - boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING; - if (needPopulate) { - // Done with scroll, no longer want to cache view drawing. - setScrollingCacheEnabled(false); - mScroller.abortAnimation(); - int oldX = getScrollX(); - int oldY = getScrollY(); - int x = mScroller.getCurrX(); - int y = mScroller.getCurrY(); - if (oldX != x || oldY != y) { - scrollTo(x, y); - if (x != oldX) { - pageScrolled(x); - } - } - } - mPopulatePending = false; - for (int i=0; i 0) || (x > getWidth() - mGutterSize && dx < 0); - } - - private void enableLayers(boolean enable) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final int layerType = enable ? - View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE; - getChildAt(i).setLayerType(layerType, null); - } - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - /* - * This method JUST determines whether we want to intercept the motion. - * If we return true, onMotionEvent will be called and we do the actual - * scrolling there. - */ - - final int action = ev.getAction() & MotionEvent.ACTION_MASK; - - // Always take care of the touch gesture being complete. - if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { - // Release the drag. - if (DEBUG) Log.v(TAG, "Intercept done!"); - mIsBeingDragged = false; - mIsUnableToDrag = false; - mActivePointerId = INVALID_POINTER; - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - return false; - } - - // Nothing more to do here if we have decided whether or not we - // are dragging. - if (action != MotionEvent.ACTION_DOWN) { - if (mIsBeingDragged) { - if (DEBUG) Log.v(TAG, "Intercept returning true!"); - return true; - } - if (mIsUnableToDrag) { - if (DEBUG) Log.v(TAG, "Intercept returning false!"); - return false; - } - } - - switch (action) { - case MotionEvent.ACTION_MOVE: { - /* - * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check - * whether the user has moved far enough from his original down touch. - */ - - /* - * Locally do absolute value. mLastMotionY is set to the y value - * of the down event. - */ - final int activePointerId = mActivePointerId; - if (activePointerId == INVALID_POINTER) { - // If we don't have a valid id, the touch down wasn't on content. - break; - } - - final int pointerIndex = ev.findPointerIndex(activePointerId); - final float x = ev.getX(pointerIndex); - final float dx = x - mLastMotionX; - final float xDiff = Math.abs(dx); - final float y = ev.getY(pointerIndex); - final float yDiff = Math.abs(y - mInitialMotionY); - if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); - - if (dx != 0 && !isGutterDrag(mLastMotionX, dx) && - canScroll(this, false, (int) dx, (int) x, (int) y)) { - // Nested view has scrollable area under this point. Let it be handled there. - mLastMotionX = x; - mLastMotionY = y; - mIsUnableToDrag = true; - return false; - } - if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) { - if (DEBUG) Log.v(TAG, "Starting drag!"); - mIsBeingDragged = true; - requestParentDisallowInterceptTouchEvent(true); - setScrollState(SCROLL_STATE_DRAGGING); - mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop : - mInitialMotionX - mTouchSlop; - mLastMotionY = y; - setScrollingCacheEnabled(true); - } else if (yDiff > mTouchSlop) { - // The finger has moved enough in the vertical - // direction to be counted as a drag... abort - // any attempt to drag horizontally, to work correctly - // with children that have scrolling containers. - if (DEBUG) Log.v(TAG, "Starting unable to drag!"); - mIsUnableToDrag = true; - } - if (mIsBeingDragged) { - // Scroll to follow the motion event - if (performDrag(x)) { - ViewPagerIcsCompat.postInvalidateOnAnimation(this); - } - } - break; - } - - case MotionEvent.ACTION_DOWN: { - /* - * Remember location of down touch. - * ACTION_DOWN always refers to pointer index 0. - */ - mLastMotionX = mInitialMotionX = ev.getX(); - mLastMotionY = mInitialMotionY = ev.getY(); - mActivePointerId = ev.getPointerId(0); - mIsUnableToDrag = false; - - mScroller.computeScrollOffset(); - if (mScrollState == SCROLL_STATE_SETTLING && - Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) { - // Let the user 'catch' the pager as it animates. - mScroller.abortAnimation(); - mPopulatePending = false; - populate(); - mIsBeingDragged = true; - requestParentDisallowInterceptTouchEvent(true); - setScrollState(SCROLL_STATE_DRAGGING); - } else { - completeScroll(false); - mIsBeingDragged = false; - } - - if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY - + " mIsBeingDragged=" + mIsBeingDragged - + "mIsUnableToDrag=" + mIsUnableToDrag); - break; - } - - case MotionEvent.ACTION_POINTER_UP: - onSecondaryPointerUp(ev); - break; - } - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - - /* - * The only time we want to intercept motion events is if we are in the - * drag mode. - */ - return mIsBeingDragged; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - if (mFakeDragging) { - // A fake drag is in progress already, ignore this real one - // but still eat the touch events. - // (It is likely that the user is multi-touching the screen.) - return true; - } - - if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { - // Don't handle edge touches immediately -- they may actually belong to one of our - // descendants. - return false; - } - - if (mAdapter == null || mAdapter.getCount() == 0) { - // Nothing to present or scroll; nothing to touch. - return false; - } - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - - final int action = ev.getAction(); - boolean needsInvalidate = false; - - switch (action & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: { - mScroller.abortAnimation(); - mPopulatePending = false; - populate(); - - // Remember where the motion event started - mLastMotionX = mInitialMotionX = ev.getX(); - mLastMotionY = mInitialMotionY = ev.getY(); - mActivePointerId = ev.getPointerId(0); - break; - } - case MotionEvent.ACTION_MOVE: - if (!mIsBeingDragged) { - final int pointerIndex = ev.findPointerIndex(mActivePointerId); - final float x = ev.getX(pointerIndex); - final float xDiff = Math.abs(x - mLastMotionX); - final float y = ev.getY(pointerIndex); - final float yDiff = Math.abs(y - mLastMotionY); - if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); - if (xDiff > mTouchSlop && xDiff > yDiff) { - if (DEBUG) Log.v(TAG, "Starting drag!"); - mIsBeingDragged = true; - requestParentDisallowInterceptTouchEvent(true); - mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop : - mInitialMotionX - mTouchSlop; - mLastMotionY = y; - setScrollState(SCROLL_STATE_DRAGGING); - setScrollingCacheEnabled(true); - - // Disallow Parent Intercept, just in case - ViewParent parent = getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } - } - } - // Not else! Note that mIsBeingDragged can be set above. - if (mIsBeingDragged) { - // Scroll to follow the motion event - final int activePointerIndex = ev.findPointerIndex(mActivePointerId); - final float x = ev.getX(activePointerIndex); - needsInvalidate |= performDrag(x); - } - break; - case MotionEvent.ACTION_UP: - if (mIsBeingDragged) { - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId); - mPopulatePending = true; - final int width = getClientWidth(); - final int scrollX = getScrollX(); - final ItemInfo ii = infoForCurrentScrollPosition(); - final int currentPage = ii.position; - final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor; - final int activePointerIndex = - ev.findPointerIndex(mActivePointerId); - final float x = ev.getX(activePointerIndex); - final int totalDelta = (int) (x - mInitialMotionX); - int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, - totalDelta); - setCurrentItemInternal(nextPage, true, true, initialVelocity); - - mActivePointerId = INVALID_POINTER; - endDrag(); - needsInvalidate = compatEdgeEffectOnRelease(mLeftEdge) | compatEdgeEffectOnRelease(mRightEdge); - } - break; - case MotionEvent.ACTION_CANCEL: - if (mIsBeingDragged) { - scrollToItem(mCurItem, true, 0, false); - mActivePointerId = INVALID_POINTER; - endDrag(); - needsInvalidate = compatEdgeEffectOnRelease(mLeftEdge) | compatEdgeEffectOnRelease(mRightEdge); - } - break; - case MotionEvent.ACTION_POINTER_DOWN: { - final int index = ev.getActionIndex(); - final float x = ev.getX(index); - mLastMotionX = x; - mActivePointerId = ev.getPointerId(index); - break; - } - case MotionEvent.ACTION_POINTER_UP: - onSecondaryPointerUp(ev); - mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId)); - break; - } - if (needsInvalidate) { - ViewPagerIcsCompat.postInvalidateOnAnimation(this); - } - return true; - } - - // implements EdgeEffectCompatIcs's onRelease() - private boolean compatEdgeEffectOnRelease(EdgeEffect eff) { - eff.onRelease(); - return eff.isFinished(); - } - - private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) { - final ViewParent parent = getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(disallowIntercept); - } - } - - private boolean performDrag(float x) { - boolean needsInvalidate = false; - - final float deltaX = mLastMotionX - x; - mLastMotionX = x; - - float oldScrollX = getScrollX(); - float scrollX = oldScrollX + deltaX; - final int width = getClientWidth(); - - float leftBound = width * mFirstOffset; - float rightBound = width * mLastOffset; - boolean leftAbsolute = true; - boolean rightAbsolute = true; - - final ItemInfo firstItem = mItems.get(0); - final ItemInfo lastItem = mItems.get(mItems.size() - 1); - if (firstItem.position != 0) { - leftAbsolute = false; - leftBound = firstItem.offset * width; - } - if (lastItem.position != mAdapter.getCount() - 1) { - rightAbsolute = false; - rightBound = lastItem.offset * width; - } - - if (scrollX < leftBound) { - if (leftAbsolute) { - float over = leftBound - scrollX; - mLeftEdge.onPull(Math.abs(over) / width); - needsInvalidate = true; // compat always returns true - } - scrollX = leftBound; - } else if (scrollX > rightBound) { - if (rightAbsolute) { - float over = scrollX - rightBound; - mRightEdge.onPull(Math.abs(over) / width); - needsInvalidate = true; // compat always returns true - } - scrollX = rightBound; - } - // Don't lose the rounded component - mLastMotionX += scrollX - (int) scrollX; - scrollTo((int) scrollX, getScrollY()); - pageScrolled((int) scrollX); - - return needsInvalidate; - } - - /** - * @return Info about the page at the current scroll position. - * This can be synthetic for a missing middle page; the 'object' field can be null. - */ - private ItemInfo infoForCurrentScrollPosition() { - final int width = getClientWidth(); - final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0; - final float marginOffset = width > 0 ? (float) mPageMargin / width : 0; - int lastPos = -1; - float lastOffset = 0.f; - float lastWidth = 0.f; - boolean first = true; - - ItemInfo lastItem = null; - for (int i = 0; i < mItems.size(); i++) { - ItemInfo ii = mItems.get(i); - float offset; - if (!first && ii.position != lastPos + 1) { - // Create a synthetic item for a missing page. - ii = mTempItem; - ii.offset = lastOffset + lastWidth + marginOffset; - ii.position = lastPos + 1; - ii.widthFactor = mAdapter.getPageWidth(ii.position); - i--; - } - offset = ii.offset; - - final float leftBound = offset; - final float rightBound = offset + ii.widthFactor + marginOffset; - if (first || scrollOffset >= leftBound) { - if (scrollOffset < rightBound || i == mItems.size() - 1) { - return ii; - } - } else { - return lastItem; - } - first = false; - lastPos = ii.position; - lastOffset = offset; - lastWidth = ii.widthFactor; - lastItem = ii; - } - - return lastItem; - } - - private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) { - int targetPage; - if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) { - targetPage = velocity > 0 ? currentPage : currentPage + 1; - } else { - final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f; - targetPage = (int) (currentPage + pageOffset + truncator); - } - - if (mItems.size() > 0) { - final ItemInfo firstItem = mItems.get(0); - final ItemInfo lastItem = mItems.get(mItems.size() - 1); - - // Only let the user target pages we have items for - targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position)); - } - - return targetPage; - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - boolean needsInvalidate = false; - - final int overScrollMode = this.getOverScrollMode(); - if (overScrollMode == View.OVER_SCROLL_ALWAYS || - (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && - mAdapter != null && mAdapter.getCount() > 1)) { - if (!mLeftEdge.isFinished()) { - final int restoreCount = canvas.save(); - final int height = getHeight() - getPaddingTop() - getPaddingBottom(); - final int width = getWidth(); - - canvas.rotate(270); - canvas.translate(-height + getPaddingTop(), mFirstOffset * width); - mLeftEdge.setSize(height, width); - needsInvalidate |= mLeftEdge.draw(canvas); - canvas.restoreToCount(restoreCount); - } - if (!mRightEdge.isFinished()) { - final int restoreCount = canvas.save(); - final int width = getWidth(); - final int height = getHeight() - getPaddingTop() - getPaddingBottom(); - - canvas.rotate(90); - canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width); - mRightEdge.setSize(height, width); - needsInvalidate |= mRightEdge.draw(canvas); - canvas.restoreToCount(restoreCount); - } - } else { - mLeftEdge.finish(); - mRightEdge.finish(); - } - - if (needsInvalidate) { - // Keep animating - ViewPagerIcsCompat.postInvalidateOnAnimation(this); - } - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - // Draw the margin drawable between pages if needed. - if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) { - final int scrollX = getScrollX(); - final int width = getWidth(); - - final float marginOffset = (float) mPageMargin / width; - int itemIndex = 0; - ItemInfo ii = mItems.get(0); - float offset = ii.offset; - final int itemCount = mItems.size(); - final int firstPos = ii.position; - final int lastPos = mItems.get(itemCount - 1).position; - for (int pos = firstPos; pos < lastPos; pos++) { - while (pos > ii.position && itemIndex < itemCount) { - ii = mItems.get(++itemIndex); - } - - float drawAt; - if (pos == ii.position) { - drawAt = (ii.offset + ii.widthFactor) * width; - offset = ii.offset + ii.widthFactor + marginOffset; - } else { - float widthFactor = mAdapter.getPageWidth(pos); - drawAt = (offset + widthFactor) * width; - offset += widthFactor + marginOffset; - } - - if (drawAt + mPageMargin > scrollX) { - mMarginDrawable.setBounds((int) drawAt, mTopPageBounds, - (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds); - mMarginDrawable.draw(canvas); - } - - if (drawAt > scrollX + width) { - break; // No more visible, no sense in continuing - } - } - } - } - - /** - * Start a fake drag of the pager. - * - *

A fake drag can be useful if you want to synchronize the motion of the ViewPager - * with the touch scrolling of another view, while still letting the ViewPager - * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.) - * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call - * {@link #endFakeDrag()} to complete the fake drag and fling as necessary. - * - *

During a fake drag the ViewPager will ignore all touch events. If a real drag - * is already in progress, this method will return false. - * - * @return true if the fake drag began successfully, false if it could not be started. - * - * @see #fakeDragBy(float) - * @see #endFakeDrag() - */ - public boolean beginFakeDrag() { - if (mIsBeingDragged) { - return false; - } - mFakeDragging = true; - setScrollState(SCROLL_STATE_DRAGGING); - mInitialMotionX = mLastMotionX = 0; - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } else { - mVelocityTracker.clear(); - } - final long time = SystemClock.uptimeMillis(); - final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0); - mVelocityTracker.addMovement(ev); - ev.recycle(); - mFakeDragBeginTime = time; - return true; - } - - /** - * End a fake drag of the pager. - * - * @see #beginFakeDrag() - * @see #fakeDragBy(float) - */ - public void endFakeDrag() { - if (!mFakeDragging) { - throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); - } - - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId); - mPopulatePending = true; - final int width = getClientWidth(); - final int scrollX = getScrollX(); - final ItemInfo ii = infoForCurrentScrollPosition(); - final int currentPage = ii.position; - final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor; - final int totalDelta = (int) (mLastMotionX - mInitialMotionX); - int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, - totalDelta); - setCurrentItemInternal(nextPage, true, true, initialVelocity); - endDrag(); - - mFakeDragging = false; - } - - /** - * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first. - * - * @param xOffset Offset in pixels to drag by. - * @see #beginFakeDrag() - * @see #endFakeDrag() - */ - public void fakeDragBy(float xOffset) { - if (!mFakeDragging) { - throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); - } - - mLastMotionX += xOffset; - - float oldScrollX = getScrollX(); - float scrollX = oldScrollX - xOffset; - final int width = getClientWidth(); - - float leftBound = width * mFirstOffset; - float rightBound = width * mLastOffset; - - final ItemInfo firstItem = mItems.get(0); - final ItemInfo lastItem = mItems.get(mItems.size() - 1); - if (firstItem.position != 0) { - leftBound = firstItem.offset * width; - } - if (lastItem.position != mAdapter.getCount() - 1) { - rightBound = lastItem.offset * width; - } - - if (scrollX < leftBound) { - scrollX = leftBound; - } else if (scrollX > rightBound) { - scrollX = rightBound; - } - // Don't lose the rounded component - mLastMotionX += scrollX - (int) scrollX; - scrollTo((int) scrollX, getScrollY()); - pageScrolled((int) scrollX); - - // Synthesize an event for the VelocityTracker. - final long time = SystemClock.uptimeMillis(); - final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, - mLastMotionX, 0, 0); - mVelocityTracker.addMovement(ev); - ev.recycle(); - } - - /** - * Returns true if a fake drag is in progress. - * - * @return true if currently in a fake drag, false otherwise. - * - * @see #beginFakeDrag() - * @see #fakeDragBy(float) - * @see #endFakeDrag() - */ - public boolean isFakeDragging() { - return mFakeDragging; - } - - private void onSecondaryPointerUp(MotionEvent ev) { - final int pointerIndex = ev.getActionIndex(); - final int pointerId = ev.getPointerId(pointerIndex); - if (pointerId == mActivePointerId) { - // This was our active pointer going up. Choose a new - // active pointer and adjust accordingly. - final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mLastMotionX = ev.getX(newPointerIndex); - mActivePointerId = ev.getPointerId(newPointerIndex); - if (mVelocityTracker != null) { - mVelocityTracker.clear(); - } - } - } - - private void endDrag() { - mIsBeingDragged = false; - mIsUnableToDrag = false; - - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - } - - private void setScrollingCacheEnabled(boolean enabled) { - if (mScrollingCacheEnabled != enabled) { - mScrollingCacheEnabled = enabled; - if (USE_CACHE) { - final int size = getChildCount(); - for (int i = 0; i < size; ++i) { - final View child = getChildAt(i); - if (child.getVisibility() != GONE) { - child.setDrawingCacheEnabled(enabled); - } - } - } - } - } - - public boolean canScrollHorizontally(int direction) { - if (mAdapter == null) { - return false; - } - - final int width = getClientWidth(); - final int scrollX = getScrollX(); - if (direction < 0) { - return (scrollX > (int) (width * mFirstOffset)); - } else if (direction > 0) { - return (scrollX < (int) (width * mLastOffset)); - } else { - return false; - } - } - - /** - * Tests scrollability within child views of v given a delta of dx. - * - * @param v View to test for horizontal scrollability - * @param checkV Whether the view v passed should itself be checked for scrollability (true), - * or just its children (false). - * @param dx Delta scrolled in pixels - * @param x X coordinate of the active touch point - * @param y Y coordinate of the active touch point - * @return true if child views of v can be scrolled by delta of dx. - */ - protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { - if (v instanceof ViewGroup) { - final ViewGroup group = (ViewGroup) v; - final int scrollX = v.getScrollX(); - final int scrollY = v.getScrollY(); - final int count = group.getChildCount(); - // Count backwards - let topmost views consume scroll distance first. - for (int i = count - 1; i >= 0; i--) { - // TODO: Add versioned support here for transformed views. - // This will not work for transformed views in Honeycomb+ - final View child = group.getChildAt(i); - if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && - y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && - canScroll(child, true, dx, x + scrollX - child.getLeft(), - y + scrollY - child.getTop())) { - return true; - } - } - } - - return checkV && v.canScrollHorizontally(-dx); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - // Let the focused view and/or our descendants get the key first - return super.dispatchKeyEvent(event) || executeKeyEvent(event); - } - - /** - * You can call this function yourself to have the scroll view perform - * scrolling from a key event, just as if the event had been dispatched to - * it by the view hierarchy. - * - * @param event The key event to execute. - * @return Return true if the event was handled, else false. - */ - public boolean executeKeyEvent(KeyEvent event) { - boolean handled = false; - if (event.getAction() == KeyEvent.ACTION_DOWN) { - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_DPAD_LEFT: - handled = arrowScroll(FOCUS_LEFT); - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - handled = arrowScroll(FOCUS_RIGHT); - break; - case KeyEvent.KEYCODE_TAB: - if (Build.VERSION.SDK_INT >= 11) { - // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD - // before Android 3.0. Ignore the tab key on those devices. - if (event.hasNoModifiers()) { - handled = arrowScroll(FOCUS_FORWARD); - } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { - handled = arrowScroll(FOCUS_BACKWARD); - } - } - break; - } - } - return handled; - } - - public boolean arrowScroll(int direction) { - View currentFocused = findFocus(); - if (currentFocused == this) { - currentFocused = null; - } else if (currentFocused != null) { - boolean isChild = false; - for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup; - parent = parent.getParent()) { - if (parent == this) { - isChild = true; - break; - } - } - if (!isChild) { - // This would cause the focus search down below to fail in fun ways. - final StringBuilder sb = new StringBuilder(); - sb.append(currentFocused.getClass().getSimpleName()); - for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup; - parent = parent.getParent()) { - sb.append(" => ").append(parent.getClass().getSimpleName()); - } - Log.e(TAG, "arrowScroll tried to find focus based on non-child " + - "current focused view " + sb.toString()); - currentFocused = null; - } - } - - boolean handled = false; - - View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, - direction); - if (nextFocused != null && nextFocused != currentFocused) { - if (direction == View.FOCUS_LEFT) { - // If there is nothing to the left, or this is causing us to - // jump to the right, then what we really want to do is page left. - final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left; - final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left; - if (currentFocused != null && nextLeft >= currLeft) { - handled = pageLeft(); - } else { - handled = nextFocused.requestFocus(); - } - } else if (direction == View.FOCUS_RIGHT) { - // If there is nothing to the right, or this is causing us to - // jump to the left, then what we really want to do is page right. - final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left; - final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left; - if (currentFocused != null && nextLeft <= currLeft) { - handled = pageRight(); - } else { - handled = nextFocused.requestFocus(); - } - } - } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) { - // Trying to move left and nothing there; try to page. - handled = pageLeft(); - } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) { - // Trying to move right and nothing there; try to page. - handled = pageRight(); - } - if (handled) { - playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); - } - return handled; - } - - private Rect getChildRectInPagerCoordinates(Rect outRect, View child) { - if (outRect == null) { - outRect = new Rect(); - } - if (child == null) { - outRect.set(0, 0, 0, 0); - return outRect; - } - outRect.left = child.getLeft(); - outRect.right = child.getRight(); - outRect.top = child.getTop(); - outRect.bottom = child.getBottom(); - - ViewParent parent = child.getParent(); - while (parent instanceof ViewGroup && parent != this) { - final ViewGroup group = (ViewGroup) parent; - outRect.left += group.getLeft(); - outRect.right += group.getRight(); - outRect.top += group.getTop(); - outRect.bottom += group.getBottom(); - - parent = group.getParent(); - } - return outRect; - } - - boolean pageLeft() { - if (mCurItem > 0) { - setCurrentItem(mCurItem-1, true); - return true; - } - return false; - } - - boolean pageRight() { - if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) { - setCurrentItem(mCurItem+1, true); - return true; - } - return false; - } - - /** - * We only want the current page that is being shown to be focusable. - */ - @Override - public void addFocusables(ArrayList views, int direction, int focusableMode) { - final int focusableCount = views.size(); - - final int descendantFocusability = getDescendantFocusability(); - - if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { - for (int i = 0; i < getChildCount(); i++) { - final View child = getChildAt(i); - if (child.getVisibility() == VISIBLE) { - ItemInfo ii = infoForChild(child); - if (ii != null && ii.position == mCurItem) { - child.addFocusables(views, direction, focusableMode); - } - } - } - } - - // we add ourselves (if focusable) in all cases except for when we are - // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is - // to avoid the focus search finding layouts when a more precise search - // among the focusable children would be more interesting. - if ( - descendantFocusability != FOCUS_AFTER_DESCENDANTS || - // No focusable descendants - (focusableCount == views.size())) { - // Note that we can't call the superclass here, because it will - // add all views in. So we need to do the same thing View does. - if (!isFocusable()) { - return; - } - if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && - isInTouchMode() && !isFocusableInTouchMode()) { - return; - } - if (views != null) { - views.add(this); - } - } - } - - /** - * We only want the current page that is being shown to be touchable. - */ - @Override - public void addTouchables(ArrayList views) { - // Note that we don't call super.addTouchables(), which means that - // we don't call View.addTouchables(). This is okay because a ViewPager - // is itself not touchable. - for (int i = 0; i < getChildCount(); i++) { - final View child = getChildAt(i); - if (child.getVisibility() == VISIBLE) { - ItemInfo ii = infoForChild(child); - if (ii != null && ii.position == mCurItem) { - child.addTouchables(views); - } - } - } - } - - /** - * We only want the current page that is being shown to be focusable. - */ - @Override - protected boolean onRequestFocusInDescendants(int direction, - Rect previouslyFocusedRect) { - int index; - int increment; - int end; - int count = getChildCount(); - if ((direction & FOCUS_FORWARD) != 0) { - index = 0; - increment = 1; - end = count; - } else { - index = count - 1; - increment = -1; - end = -1; - } - for (int i = index; i != end; i += increment) { - View child = getChildAt(i); - if (child.getVisibility() == VISIBLE) { - ItemInfo ii = infoForChild(child); - if (ii != null && ii.position == mCurItem) { - if (child.requestFocus(direction, previouslyFocusedRect)) { - return true; - } - } - } - } - return false; - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - // Dispatch scroll events from this ViewPager. - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { - return super.dispatchPopulateAccessibilityEvent(event); - } - - // Dispatch all other accessibility events from the current page. - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == VISIBLE) { - final ItemInfo ii = infoForChild(child); - if (ii != null && ii.position == mCurItem && - child.dispatchPopulateAccessibilityEvent(event)) { - return true; - } - } - } - - return false; - } - - @Override - protected ViewGroup.LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(); - } - - @Override - protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - return generateDefaultLayoutParams(); - } - - @Override - protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof LayoutParams && super.checkLayoutParams(p); - } - - @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - return new LayoutParams(getContext(), attrs); - } - - class MyAccessibilityDelegate extends AccessibilityDelegate { - - @Override - public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(host, event); - event.setClassName(ViewPager.class.getName()); - final AccessibilityRecord recordCompat = AccessibilityRecord.obtain(); - recordCompat.setScrollable(canScroll()); - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED - && mAdapter != null) { - recordCompat.setItemCount(mAdapter.getCount()); - recordCompat.setFromIndex(mCurItem); - recordCompat.setToIndex(mCurItem); - } - } - - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - info.setClassName(ViewPager.class.getName()); - info.setScrollable(canScroll()); - if (canScrollHorizontally(1)) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); - } - if (canScrollHorizontally(-1)) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); - } - } - - @Override - public boolean performAccessibilityAction(View host, int action, Bundle args) { - if (super.performAccessibilityAction(host, action, args)) { - return true; - } - switch (action) { - case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { - if (canScrollHorizontally(1)) { - setCurrentItem(mCurItem + 1); - return true; - } - } return false; - case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { - if (canScrollHorizontally(-1)) { - setCurrentItem(mCurItem - 1); - return true; - } - } return false; - } - return false; - } - - private boolean canScroll() { - return (mAdapter != null) && (mAdapter.getCount() > 1); - } - } - - private class PagerObserver extends DataSetObserver { - @Override - public void onChanged() { - dataSetChanged(); - } - @Override - public void onInvalidated() { - dataSetChanged(); - } - } - - /** - * Layout parameters that should be supplied for views added to a - * ViewPager. - */ - public static class LayoutParams extends ViewGroup.LayoutParams { - /** - * true if this view is a decoration on the pager itself and not - * a view supplied by the adapter. - */ - public boolean isDecor; - - /** - * Gravity setting for use on decor views only: - * Where to position the view page within the overall ViewPager - * container; constants are defined in {@link android.view.Gravity}. - */ - public int gravity; - - /** - * Width as a 0-1 multiplier of the measured pager width - */ - float widthFactor = 0.f; - - /** - * true if this view was added during layout and needs to be measured - * before being positioned. - */ - boolean needsMeasure; - - /** - * Adapter position this view is for if !isDecor - */ - int position; - - /** - * Current child index within the ViewPager that this view occupies - */ - int childIndex; - - public LayoutParams() { - super(FILL_PARENT, FILL_PARENT); - } - - public LayoutParams(Context context, AttributeSet attrs) { - super(context, attrs); - - final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); - gravity = a.getInteger(0, Gravity.TOP); - a.recycle(); - } - } - - static class ViewPositionComparator implements Comparator { - @Override - public int compare(View lhs, View rhs) { - final LayoutParams llp = (LayoutParams) lhs.getLayoutParams(); - final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams(); - if (llp.isDecor != rlp.isDecor) { - return llp.isDecor ? 1 : -1; - } - return llp.position - rlp.position; - } - } -} diff --git a/app/src/main/java/android/support/v4/view/ViewPagerIcsCompat.java b/app/src/main/java/android/support/v4/view/ViewPagerIcsCompat.java deleted file mode 100644 index b6b8698d..00000000 --- a/app/src/main/java/android/support/v4/view/ViewPagerIcsCompat.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2011 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.view; - -import android.os.Build; -import android.view.View; - - -/** - * Bare bones compat library to support ICS (API level 15) - * for Vanilla Music - */ -public class ViewPagerIcsCompat { - // From ViewPagerCompat - private static final long FAKE_FRAME_TIME = 10; - - public static int getImportantForAccessibility(View view) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - return view.getImportantForAccessibility(); - } else { - return -1; // never returned by real implementation - } - } - - public static void postInvalidateOnAnimation(View view) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - view.postInvalidateOnAnimation(); - } else { - view.postInvalidateDelayed(FAKE_FRAME_TIME); - } - } - - public static void postOnAnimation(View view, Runnable action) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - view.postOnAnimation(action); - } else { - view.postDelayed(action, FAKE_FRAME_TIME); - } - } -}