Added prebuilts for SDK for march release of samples
Change-Id: I652b5fa255297e8dfe1d98e0ab083c09067749a3
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/build.gradle b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/build.gradle
new file mode 100644
index 0000000..c39392a
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/build.gradle
@@ -0,0 +1,62 @@
+
+
+
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.8.+'
+ }
+}
+
+apply plugin: 'android'
+
+dependencies {
+
+ // Add the support lib that is appropriate for SDK 19
+ compile "com.android.support:support-v13:19.0.+"
+
+
+}
+
+// The sample build uses multiple directories to
+// keep boilerplate and common code separate from
+// the main sample code.
+List<String> dirs = [
+ 'main', // main sample code; look here for the interesting stuff.
+ 'common', // components that are reused by multiple samples
+ 'template'] // boilerplate code that is generated by the sample template process
+
+android {
+ compileSdkVersion 19
+
+ buildToolsVersion "19.0.1"
+
+ sourceSets {
+ main {
+ dirs.each { dir ->
+ java.srcDirs "src/${dir}/java"
+ res.srcDirs "src/${dir}/res"
+ }
+ }
+ instrumentTest.setRoot('tests')
+ instrumentTest.java.srcDirs = ['tests/src']
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/AndroidManifest.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..01b414d
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2014 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.adaptertransition"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk
+ android:minSdkVersion="19"
+ android:targetSdkVersion="19"/>
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme">
+ <activity
+ android:name="com.example.android.adaptertransition.MainActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/AdapterTransitionFragment.java b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/AdapterTransitionFragment.java
new file mode 100644
index 0000000..525ed40
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/AdapterTransitionFragment.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2014 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 com.example.android.adaptertransition;
+
+import android.os.Bundle;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.Fragment;
+import android.transition.AutoTransition;
+import android.transition.Scene;
+import android.transition.Transition;
+import android.transition.TransitionManager;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.FrameLayout;
+import android.widget.GridView;
+import android.widget.ListView;
+
+/**
+ * Main screen for AdapterTransition sample.
+ */
+public class AdapterTransitionFragment extends Fragment implements Transition.TransitionListener {
+
+ /**
+ * Since the transition framework requires all relevant views in a view hierarchy to be marked
+ * with IDs, we use this ID to mark the root view.
+ */
+ private static final int ROOT_ID = 1;
+
+ /**
+ * This is where we place our AdapterView (ListView / GridView).
+ */
+ private FrameLayout mContent;
+
+ /**
+ * This is where we carry out the transition.
+ */
+ private FrameLayout mCover;
+
+ /**
+ * This list shows our contents. It can be ListView or GridView, and we toggle between them
+ * using the transition framework.
+ */
+ private AbsListView mAbsListView;
+
+ /**
+ * This is our contents.
+ */
+ private MeatAdapter mAdapter;
+
+ public static AdapterTransitionFragment newInstance() {
+ return new AdapterTransitionFragment();
+ }
+
+ public AdapterTransitionFragment() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ // We use a ListView at first
+ mAbsListView = (AbsListView) inflater.inflate(R.layout.fragment_meat_list, container, false);
+ mAdapter = new MeatAdapter(inflater, R.layout.item_meat_list);
+ return inflater.inflate(R.layout.fragment_adapter_transition, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ // Retaining references for FrameLayouts that we use later.
+ mContent = (FrameLayout) view.findViewById(R.id.content);
+ mCover = (FrameLayout) view.findViewById(R.id.cover);
+ // We are attaching the list to the screen here.
+ mAbsListView.setAdapter(mAdapter);
+ mContent.addView(mAbsListView);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.fragment_adapter_transition, menu);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ // We change the look of the icon every time the user toggles between list and grid.
+ MenuItem item = menu.findItem(R.id.action_toggle);
+ if (null != item) {
+ if (mAbsListView instanceof ListView) {
+ item.setIcon(R.drawable.ic_action_grid);
+ } else {
+ item.setIcon(R.drawable.ic_action_list);
+ }
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_toggle: {
+ toggle();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onTransitionStart(Transition transition) {
+ }
+
+ // BEGIN_INCLUDE(on_transition_end)
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ // When the transition ends, we remove all the views from the overlay and hide it.
+ mCover.removeAllViews();
+ mCover.setVisibility(View.INVISIBLE);
+ }
+ // END_INCLUDE(on_transition_end)
+
+ @Override
+ public void onTransitionCancel(Transition transition) {
+ }
+
+ @Override
+ public void onTransitionPause(Transition transition) {
+ }
+
+ @Override
+ public void onTransitionResume(Transition transition) {
+ }
+
+ /**
+ * Toggle the UI between ListView and GridView.
+ */
+ private void toggle() {
+ // We use mCover as the overlay on which we carry out the transition.
+ mCover.setVisibility(View.VISIBLE);
+ // This FrameLayout holds all the visible views in the current list or grid. We use this as
+ // the starting Scene of the Transition later.
+ FrameLayout before = copyVisibleViews();
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
+ mCover.addView(before, params);
+ // Swap the actual list.
+ swapAbsListView();
+ // We also swap the icon for the toggle button.
+ ActivityCompat.invalidateOptionsMenu(getActivity());
+ // It is now ready to start the transition.
+ mAbsListView.post(new Runnable() {
+ @Override
+ public void run() {
+ // BEGIN_INCLUDE(transition_with_listener)
+ Scene scene = new Scene(mCover, copyVisibleViews());
+ Transition transition = new AutoTransition();
+ transition.addListener(AdapterTransitionFragment.this);
+ TransitionManager.go(scene, transition);
+ // END_INCLUDE(transition_with_listener)
+ }
+ });
+ }
+
+ /**
+ * Swap ListView with GridView, or GridView with ListView.
+ */
+ private void swapAbsListView() {
+ // We save the current scrolling position before removing the current list.
+ int first = mAbsListView.getFirstVisiblePosition();
+ // If the current list is a GridView, we replace it with a ListView. If it is a ListView,
+ // a GridView.
+ LayoutInflater inflater = LayoutInflater.from(getActivity());
+ if (mAbsListView instanceof GridView) {
+ mAbsListView = (AbsListView) inflater.inflate(
+ R.layout.fragment_meat_list, (ViewGroup) mAbsListView.getParent(), false);
+ mAdapter = new MeatAdapter(inflater, R.layout.item_meat_list);
+ } else {
+ mAbsListView = (AbsListView) inflater.inflate(
+ R.layout.fragment_meat_grid, (ViewGroup) mAbsListView.getParent(), false);
+ mAdapter = new MeatAdapter(inflater, R.layout.item_meat_grid);
+ }
+ mAbsListView.setAdapter(mAdapter);
+ // We restore the scrolling position here.
+ mAbsListView.setSelection(first);
+ // The new list is ready, and we replace the existing one with it.
+ mContent.removeAllViews();
+ mContent.addView(mAbsListView);
+ }
+
+ /**
+ * Copy all the visible views in the mAbsListView into a new FrameLayout and return it.
+ *
+ * @return a FrameLayout with all the visible views inside.
+ */
+ private FrameLayout copyVisibleViews() {
+ // This is the FrameLayout we return afterwards.
+ FrameLayout layout = new FrameLayout(getActivity());
+ // The transition framework requires to set ID for all views to be animated.
+ layout.setId(ROOT_ID);
+ // We only copy visible views.
+ int first = mAbsListView.getFirstVisiblePosition();
+ int index = 0;
+ while (true) {
+ // This is one of the views that we copy. Note that the argument for getChildAt is a
+ // zero-oriented index, and it doesn't usually match with its position in the list.
+ View source = mAbsListView.getChildAt(index);
+ if (null == source) {
+ break;
+ }
+ // This is the copy of the original view.
+ View destination = mAdapter.getView(first + index, null, layout);
+ assert destination != null;
+ destination.setId(ROOT_ID + first + index);
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+ source.getWidth(), source.getHeight());
+ params.leftMargin = (int) source.getX();
+ params.topMargin = (int) source.getY();
+ layout.addView(destination, params);
+ ++index;
+ }
+ return layout;
+ }
+
+}
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/MainActivity.java b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/MainActivity.java
new file mode 100644
index 0000000..a45632c
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/MainActivity.java
@@ -0,0 +1,110 @@
+/*
+* Copyright 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 com.example.android.adaptertransition;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentTransaction;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.ViewAnimator;
+
+import com.example.android.common.activities.SampleActivityBase;
+import com.example.android.common.logger.Log;
+import com.example.android.common.logger.LogFragment;
+import com.example.android.common.logger.LogWrapper;
+import com.example.android.common.logger.MessageOnlyLogFilter;
+
+/**
+ * A simple launcher activity containing a summary sample description, sample log and a custom
+ * {@link android.support.v4.app.Fragment} which can display a view.
+ * <p>
+ * For devices with displays with a width of 720dp or greater, the sample log is always visible,
+ * on other devices it's visibility is controlled by an item on the Action Bar.
+ */
+public class MainActivity extends SampleActivityBase {
+
+ public static final String TAG = "MainActivity";
+
+ // Whether the Log Fragment is currently shown
+ private boolean mLogShown;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ AdapterTransitionFragment fragment = new AdapterTransitionFragment();
+ transaction.replace(R.id.sample_content_fragment, fragment);
+ transaction.commit();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem logToggle = menu.findItem(R.id.menu_toggle_log);
+ logToggle.setVisible(findViewById(R.id.sample_output) instanceof ViewAnimator);
+ logToggle.setTitle(mLogShown ? R.string.sample_hide_log : R.string.sample_show_log);
+
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case R.id.menu_toggle_log:
+ mLogShown = !mLogShown;
+ ViewAnimator output = (ViewAnimator) findViewById(R.id.sample_output);
+ if (mLogShown) {
+ output.setDisplayedChild(1);
+ } else {
+ output.setDisplayedChild(0);
+ }
+ supportInvalidateOptionsMenu();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ /** Create a chain of targets that will receive log data */
+ @Override
+ public void initializeLogging() {
+ // Wraps Android's native log framework.
+ LogWrapper logWrapper = new LogWrapper();
+ // Using Log, front-end to the logging chain, emulates android.util.log method signatures.
+ Log.setLogNode(logWrapper);
+
+ // Filter strips out everything except the message text.
+ MessageOnlyLogFilter msgFilter = new MessageOnlyLogFilter();
+ logWrapper.setNext(msgFilter);
+
+ // On screen logging via a fragment with a TextView.
+ LogFragment logFragment = (LogFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.log_fragment);
+ msgFilter.setNext(logFragment.getLogView());
+
+ Log.i(TAG, "Ready");
+ }
+}
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/Meat.java b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/Meat.java
new file mode 100644
index 0000000..bca1c5f
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/Meat.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014 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 com.example.android.adaptertransition;
+
+/**
+ * Sample data.
+ */
+public class Meat {
+
+ public int resourceId;
+ public String title;
+
+ public Meat(int resourceId, String title) {
+ this.resourceId = resourceId;
+ this.title = title;
+ }
+
+ public static final Meat[] MEATS = {
+ new Meat(R.drawable.p1, "First"),
+ new Meat(R.drawable.p2, "Second"),
+ new Meat(R.drawable.p3, "Third"),
+ new Meat(R.drawable.p4, "Fourth"),
+ new Meat(R.drawable.p5, "Fifth"),
+ new Meat(R.drawable.p6, "Sixth"),
+ new Meat(R.drawable.p7, "Seventh"),
+ new Meat(R.drawable.p8, "Eighth"),
+ new Meat(R.drawable.p9, "Ninth"),
+ new Meat(R.drawable.p10, "Tenth"),
+ new Meat(R.drawable.p11, "Eleventh"),
+ };
+
+}
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/MeatAdapter.java b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/MeatAdapter.java
new file mode 100644
index 0000000..c7630ce
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/MeatAdapter.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2014 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 com.example.android.adaptertransition;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * This class provides data as Views. It is designed to support both ListView and GridView by
+ * changing a layout resource file to inflate.
+ */
+public class MeatAdapter extends BaseAdapter {
+
+ private final LayoutInflater mLayoutInflater;
+ private final int mResourceId;
+
+ /**
+ * Create a new instance of {@link MeatAdapter}.
+ *
+ * @param inflater The layout inflater.
+ * @param resourceId The resource ID for the layout to be used. The layout should contain an
+ * ImageView with ID of "meat_image" and a TextView with ID of "meat_title".
+ */
+ public MeatAdapter(LayoutInflater inflater, int resourceId) {
+ mLayoutInflater = inflater;
+ mResourceId = resourceId;
+ }
+
+ @Override
+ public int getCount() {
+ return Meat.MEATS.length;
+ }
+
+ @Override
+ public Meat getItem(int position) {
+ return Meat.MEATS[position];
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return Meat.MEATS[position].resourceId;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final View view;
+ final ViewHolder holder;
+ if (null == convertView) {
+ view = mLayoutInflater.inflate(mResourceId, parent, false);
+ holder = new ViewHolder();
+ assert view != null;
+ holder.image = (ImageView) view.findViewById(R.id.meat_image);
+ holder.title = (TextView) view.findViewById(R.id.meat_title);
+ view.setTag(holder);
+ } else {
+ view = convertView;
+ holder = (ViewHolder) view.getTag();
+ }
+ Meat meat = getItem(position);
+ holder.image.setImageResource(meat.resourceId);
+ holder.title.setText(meat.title);
+ return view;
+ }
+
+ private static class ViewHolder {
+ public ImageView image;
+ public TextView title;
+ }
+
+}
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/activities/SampleActivityBase.java b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/activities/SampleActivityBase.java
new file mode 100644
index 0000000..3228927
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/activities/SampleActivityBase.java
@@ -0,0 +1,52 @@
+/*
+* Copyright 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 com.example.android.common.activities;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+
+import com.example.android.common.logger.Log;
+import com.example.android.common.logger.LogWrapper;
+
+/**
+ * Base launcher activity, to handle most of the common plumbing for samples.
+ */
+public class SampleActivityBase extends FragmentActivity {
+
+ public static final String TAG = "SampleActivityBase";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ initializeLogging();
+ }
+
+ /** Set up targets to receive log data */
+ public void initializeLogging() {
+ // Using Log, front-end to the logging chain, emulates android.util.log method signatures.
+ // Wraps Android's native log framework
+ LogWrapper logWrapper = new LogWrapper();
+ Log.setLogNode(logWrapper);
+
+ Log.i(TAG, "Ready");
+ }
+}
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/Log.java b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/Log.java
new file mode 100644
index 0000000..17503c5
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/Log.java
@@ -0,0 +1,236 @@
+/*
+ * 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 com.example.android.common.logger;
+
+/**
+ * Helper class for a list (or tree) of LoggerNodes.
+ *
+ * <p>When this is set as the head of the list,
+ * an instance of it can function as a drop-in replacement for {@link android.util.Log}.
+ * Most of the methods in this class server only to map a method call in Log to its equivalent
+ * in LogNode.</p>
+ */
+public class Log {
+ // Grabbing the native values from Android's native logging facilities,
+ // to make for easy migration and interop.
+ public static final int NONE = -1;
+ public static final int VERBOSE = android.util.Log.VERBOSE;
+ public static final int DEBUG = android.util.Log.DEBUG;
+ public static final int INFO = android.util.Log.INFO;
+ public static final int WARN = android.util.Log.WARN;
+ public static final int ERROR = android.util.Log.ERROR;
+ public static final int ASSERT = android.util.Log.ASSERT;
+
+ // Stores the beginning of the LogNode topology.
+ private static LogNode mLogNode;
+
+ /**
+ * Returns the next LogNode in the linked list.
+ */
+ public static LogNode getLogNode() {
+ return mLogNode;
+ }
+
+ /**
+ * Sets the LogNode data will be sent to.
+ */
+ public static void setLogNode(LogNode node) {
+ mLogNode = node;
+ }
+
+ /**
+ * Instructs the LogNode to print the log data provided. Other LogNodes can
+ * be chained to the end of the LogNode as desired.
+ *
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void println(int priority, String tag, String msg, Throwable tr) {
+ if (mLogNode != null) {
+ mLogNode.println(priority, tag, msg, tr);
+ }
+ }
+
+ /**
+ * Instructs the LogNode to print the log data provided. Other LogNodes can
+ * be chained to the end of the LogNode as desired.
+ *
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ */
+ public static void println(int priority, String tag, String msg) {
+ println(priority, tag, msg, null);
+ }
+
+ /**
+ * Prints a message at VERBOSE priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void v(String tag, String msg, Throwable tr) {
+ println(VERBOSE, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at VERBOSE priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void v(String tag, String msg) {
+ v(tag, msg, null);
+ }
+
+
+ /**
+ * Prints a message at DEBUG priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void d(String tag, String msg, Throwable tr) {
+ println(DEBUG, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at DEBUG priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void d(String tag, String msg) {
+ d(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at INFO priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void i(String tag, String msg, Throwable tr) {
+ println(INFO, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at INFO priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void i(String tag, String msg) {
+ i(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void w(String tag, String msg, Throwable tr) {
+ println(WARN, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void w(String tag, String msg) {
+ w(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void w(String tag, Throwable tr) {
+ w(tag, null, tr);
+ }
+
+ /**
+ * Prints a message at ERROR priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void e(String tag, String msg, Throwable tr) {
+ println(ERROR, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at ERROR priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void e(String tag, String msg) {
+ e(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void wtf(String tag, String msg, Throwable tr) {
+ println(ASSERT, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void wtf(String tag, String msg) {
+ wtf(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void wtf(String tag, Throwable tr) {
+ wtf(tag, null, tr);
+ }
+}
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/LogFragment.java b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/LogFragment.java
new file mode 100644
index 0000000..b302acd
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/LogFragment.java
@@ -0,0 +1,109 @@
+/*
+* Copyright 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.
+*/
+/*
+ * Copyright 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 com.example.android.common.logger;
+
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ScrollView;
+
+/**
+ * Simple fraggment which contains a LogView and uses is to output log data it receives
+ * through the LogNode interface.
+ */
+public class LogFragment extends Fragment {
+
+ private LogView mLogView;
+ private ScrollView mScrollView;
+
+ public LogFragment() {}
+
+ public View inflateViews() {
+ mScrollView = new ScrollView(getActivity());
+ ViewGroup.LayoutParams scrollParams = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ mScrollView.setLayoutParams(scrollParams);
+
+ mLogView = new LogView(getActivity());
+ ViewGroup.LayoutParams logParams = new ViewGroup.LayoutParams(scrollParams);
+ logParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ mLogView.setLayoutParams(logParams);
+ mLogView.setClickable(true);
+ mLogView.setFocusable(true);
+ mLogView.setTypeface(Typeface.MONOSPACE);
+
+ // Want to set padding as 16 dips, setPadding takes pixels. Hooray math!
+ int paddingDips = 16;
+ double scale = getResources().getDisplayMetrics().density;
+ int paddingPixels = (int) ((paddingDips * (scale)) + .5);
+ mLogView.setPadding(paddingPixels, paddingPixels, paddingPixels, paddingPixels);
+ mLogView.setCompoundDrawablePadding(paddingPixels);
+
+ mLogView.setGravity(Gravity.BOTTOM);
+ mLogView.setTextAppearance(getActivity(), android.R.style.TextAppearance_Holo_Medium);
+
+ mScrollView.addView(mLogView);
+ return mScrollView;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ View result = inflateViews();
+
+ mLogView.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
+ }
+ });
+ return result;
+ }
+
+ public LogView getLogView() {
+ return mLogView;
+ }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/LogNode.java b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/LogNode.java
new file mode 100644
index 0000000..bc37cab
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/LogNode.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.common.logger;
+
+/**
+ * Basic interface for a logging system that can output to one or more targets.
+ * Note that in addition to classes that will output these logs in some format,
+ * one can also implement this interface over a filter and insert that in the chain,
+ * such that no targets further down see certain data, or see manipulated forms of the data.
+ * You could, for instance, write a "ToHtmlLoggerNode" that just converted all the log data
+ * it received to HTML and sent it along to the next node in the chain, without printing it
+ * anywhere.
+ */
+public interface LogNode {
+
+ /**
+ * Instructs first LogNode in the list to print the log data provided.
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public void println(int priority, String tag, String msg, Throwable tr);
+
+}
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/LogView.java b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/LogView.java
new file mode 100644
index 0000000..c01542b
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/LogView.java
@@ -0,0 +1,145 @@
+/*
+ * 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 com.example.android.common.logger;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.*;
+import android.widget.TextView;
+
+/** Simple TextView which is used to output log data received through the LogNode interface.
+*/
+public class LogView extends TextView implements LogNode {
+
+ public LogView(Context context) {
+ super(context);
+ }
+
+ public LogView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public LogView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * Formats the log data and prints it out to the LogView.
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ @Override
+ public void println(int priority, String tag, String msg, Throwable tr) {
+
+
+ String priorityStr = null;
+
+ // For the purposes of this View, we want to print the priority as readable text.
+ switch(priority) {
+ case android.util.Log.VERBOSE:
+ priorityStr = "VERBOSE";
+ break;
+ case android.util.Log.DEBUG:
+ priorityStr = "DEBUG";
+ break;
+ case android.util.Log.INFO:
+ priorityStr = "INFO";
+ break;
+ case android.util.Log.WARN:
+ priorityStr = "WARN";
+ break;
+ case android.util.Log.ERROR:
+ priorityStr = "ERROR";
+ break;
+ case android.util.Log.ASSERT:
+ priorityStr = "ASSERT";
+ break;
+ default:
+ break;
+ }
+
+ // Handily, the Log class has a facility for converting a stack trace into a usable string.
+ String exceptionStr = null;
+ if (tr != null) {
+ exceptionStr = android.util.Log.getStackTraceString(tr);
+ }
+
+ // Take the priority, tag, message, and exception, and concatenate as necessary
+ // into one usable line of text.
+ final StringBuilder outputBuilder = new StringBuilder();
+
+ String delimiter = "\t";
+ appendIfNotNull(outputBuilder, priorityStr, delimiter);
+ appendIfNotNull(outputBuilder, tag, delimiter);
+ appendIfNotNull(outputBuilder, msg, delimiter);
+ appendIfNotNull(outputBuilder, exceptionStr, delimiter);
+
+ // In case this was originally called from an AsyncTask or some other off-UI thread,
+ // make sure the update occurs within the UI thread.
+ ((Activity) getContext()).runOnUiThread( (new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // Display the text we just generated within the LogView.
+ appendToLog(outputBuilder.toString());
+ }
+ })));
+
+ if (mNext != null) {
+ mNext.println(priority, tag, msg, tr);
+ }
+ }
+
+ public LogNode getNext() {
+ return mNext;
+ }
+
+ public void setNext(LogNode node) {
+ mNext = node;
+ }
+
+ /** Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since
+ * the logger takes so many arguments that might be null, this method helps cut out some of the
+ * agonizing tedium of writing the same 3 lines over and over.
+ * @param source StringBuilder containing the text to append to.
+ * @param addStr The String to append
+ * @param delimiter The String to separate the source and appended strings. A tab or comma,
+ * for instance.
+ * @return The fully concatenated String as a StringBuilder
+ */
+ private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) {
+ if (addStr != null) {
+ if (addStr.length() == 0) {
+ delimiter = "";
+ }
+
+ return source.append(addStr).append(delimiter);
+ }
+ return source;
+ }
+
+ // The next LogNode in the chain.
+ LogNode mNext;
+
+ /** Outputs the string as a new line of log data in the LogView. */
+ public void appendToLog(String s) {
+ append("\n" + s);
+ }
+
+
+}
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/LogWrapper.java b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/LogWrapper.java
new file mode 100644
index 0000000..16a9e7b
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/LogWrapper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.common.logger;
+
+import android.util.Log;
+
+/**
+ * Helper class which wraps Android's native Log utility in the Logger interface. This way
+ * normal DDMS output can be one of the many targets receiving and outputting logs simultaneously.
+ */
+public class LogWrapper implements LogNode {
+
+ // For piping: The next node to receive Log data after this one has done its work.
+ private LogNode mNext;
+
+ /**
+ * Returns the next LogNode in the linked list.
+ */
+ public LogNode getNext() {
+ return mNext;
+ }
+
+ /**
+ * Sets the LogNode data will be sent to..
+ */
+ public void setNext(LogNode node) {
+ mNext = node;
+ }
+
+ /**
+ * Prints data out to the console using Android's native log mechanism.
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ @Override
+ public void println(int priority, String tag, String msg, Throwable tr) {
+ // There actually are log methods that don't take a msg parameter. For now,
+ // if that's the case, just convert null to the empty string and move on.
+ String useMsg = msg;
+ if (useMsg == null) {
+ useMsg = "";
+ }
+
+ // If an exeption was provided, convert that exception to a usable string and attach
+ // it to the end of the msg method.
+ if (tr != null) {
+ msg += "\n" + Log.getStackTraceString(tr);
+ }
+
+ // This is functionally identical to Log.x(tag, useMsg);
+ // For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg)
+ Log.println(priority, tag, useMsg);
+
+ // If this isn't the last node in the chain, move things along.
+ if (mNext != null) {
+ mNext.println(priority, tag, msg, tr);
+ }
+ }
+}
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java
new file mode 100644
index 0000000..19967dc
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java
@@ -0,0 +1,60 @@
+/*
+ * 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 com.example.android.common.logger;
+
+/**
+ * Simple {@link LogNode} filter, removes everything except the message.
+ * Useful for situations like on-screen log output where you don't want a lot of metadata displayed,
+ * just easy-to-read message updates as they're happening.
+ */
+public class MessageOnlyLogFilter implements LogNode {
+
+ LogNode mNext;
+
+ /**
+ * Takes the "next" LogNode as a parameter, to simplify chaining.
+ *
+ * @param next The next LogNode in the pipeline.
+ */
+ public MessageOnlyLogFilter(LogNode next) {
+ mNext = next;
+ }
+
+ public MessageOnlyLogFilter() {
+ }
+
+ @Override
+ public void println(int priority, String tag, String msg, Throwable tr) {
+ if (mNext != null) {
+ getNext().println(Log.NONE, null, msg, null);
+ }
+ }
+
+ /**
+ * Returns the next LogNode in the chain.
+ */
+ public LogNode getNext() {
+ return mNext;
+ }
+
+ /**
+ * Sets the LogNode data will be sent to..
+ */
+ public void setNext(LogNode node) {
+ mNext = node;
+ }
+
+}
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/view/SlidingTabLayout.java b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/view/SlidingTabLayout.java
new file mode 100644
index 0000000..20049e3
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/view/SlidingTabLayout.java
@@ -0,0 +1,314 @@
+/*
+ * 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 com.example.android.common.view;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.HorizontalScrollView;
+import android.widget.TextView;
+
+/**
+ * To be used with ViewPager to provide a tab indicator component which give constant feedback as to
+ * the user's scroll progress.
+ * <p>
+ * To use the component, simply add it to your view hierarchy. Then in your
+ * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call
+ * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for.
+ * <p>
+ * The colors can be customized in two ways. The first and simplest is to provide an array of colors
+ * via {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)}. The
+ * alternative is via the {@link TabColorizer} interface which provides you complete control over
+ * which color is used for any individual position.
+ * <p>
+ * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)},
+ * providing the layout ID of your custom layout.
+ */
+public class SlidingTabLayout extends HorizontalScrollView {
+
+ /**
+ * Allows complete control over the colors drawn in the tab layout. Set with
+ * {@link #setCustomTabColorizer(TabColorizer)}.
+ */
+ public interface TabColorizer {
+
+ /**
+ * @return return the color of the indicator used when {@code position} is selected.
+ */
+ int getIndicatorColor(int position);
+
+ /**
+ * @return return the color of the divider drawn to the right of {@code position}.
+ */
+ int getDividerColor(int position);
+
+ }
+
+ private static final int TITLE_OFFSET_DIPS = 24;
+ private static final int TAB_VIEW_PADDING_DIPS = 16;
+ private static final int TAB_VIEW_TEXT_SIZE_SP = 12;
+
+ private int mTitleOffset;
+
+ private int mTabViewLayoutId;
+ private int mTabViewTextViewId;
+
+ private ViewPager mViewPager;
+ private ViewPager.OnPageChangeListener mViewPagerPageChangeListener;
+
+ private final SlidingTabStrip mTabStrip;
+
+ public SlidingTabLayout(Context context) {
+ this(context, null);
+ }
+
+ public SlidingTabLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ // Disable the Scroll Bar
+ setHorizontalScrollBarEnabled(false);
+ // Make sure that the Tab Strips fills this View
+ setFillViewport(true);
+
+ mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
+
+ mTabStrip = new SlidingTabStrip(context);
+ addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ /**
+ * Set the custom {@link TabColorizer} to be used.
+ *
+ * If you only require simple custmisation then you can use
+ * {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)} to achieve
+ * similar effects.
+ */
+ public void setCustomTabColorizer(TabColorizer tabColorizer) {
+ mTabStrip.setCustomTabColorizer(tabColorizer);
+ }
+
+ /**
+ * Sets the colors to be used for indicating the selected tab. These colors are treated as a
+ * circular array. Providing one color will mean that all tabs are indicated with the same color.
+ */
+ public void setSelectedIndicatorColors(int... colors) {
+ mTabStrip.setSelectedIndicatorColors(colors);
+ }
+
+ /**
+ * Sets the colors to be used for tab dividers. These colors are treated as a circular array.
+ * Providing one color will mean that all tabs are indicated with the same color.
+ */
+ public void setDividerColors(int... colors) {
+ mTabStrip.setDividerColors(colors);
+ }
+
+ /**
+ * Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are
+ * required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so
+ * that the layout can update it's scroll position correctly.
+ *
+ * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener)
+ */
+ public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
+ mViewPagerPageChangeListener = listener;
+ }
+
+ /**
+ * Set the custom layout to be inflated for the tab views.
+ *
+ * @param layoutResId Layout id to be inflated
+ * @param textViewId id of the {@link TextView} in the inflated view
+ */
+ public void setCustomTabView(int layoutResId, int textViewId) {
+ mTabViewLayoutId = layoutResId;
+ mTabViewTextViewId = textViewId;
+ }
+
+ /**
+ * Sets the associated view pager. Note that the assumption here is that the pager content
+ * (number of tabs and tab titles) does not change after this call has been made.
+ */
+ public void setViewPager(ViewPager viewPager) {
+ mTabStrip.removeAllViews();
+
+ mViewPager = viewPager;
+ if (viewPager != null) {
+ viewPager.setOnPageChangeListener(new InternalViewPagerListener());
+ populateTabStrip();
+ }
+ }
+
+ /**
+ * Create a default view to be used for tabs. This is called if a custom tab view is not set via
+ * {@link #setCustomTabView(int, int)}.
+ */
+ protected TextView createDefaultTabView(Context context) {
+ TextView textView = new TextView(context);
+ textView.setGravity(Gravity.CENTER);
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP);
+ textView.setTypeface(Typeface.DEFAULT_BOLD);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ // If we're running on Honeycomb or newer, then we can use the Theme's
+ // selectableItemBackground to ensure that the View has a pressed state
+ TypedValue outValue = new TypedValue();
+ getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground,
+ outValue, true);
+ textView.setBackgroundResource(outValue.resourceId);
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ // If we're running on ICS or newer, enable all-caps to match the Action Bar tab style
+ textView.setAllCaps(true);
+ }
+
+ int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density);
+ textView.setPadding(padding, padding, padding, padding);
+
+ return textView;
+ }
+
+ private void populateTabStrip() {
+ final PagerAdapter adapter = mViewPager.getAdapter();
+ final View.OnClickListener tabClickListener = new TabClickListener();
+
+ for (int i = 0; i < adapter.getCount(); i++) {
+ View tabView = null;
+ TextView tabTitleView = null;
+
+ if (mTabViewLayoutId != 0) {
+ // If there is a custom tab view layout id set, try and inflate it
+ tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip,
+ false);
+ tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId);
+ }
+
+ if (tabView == null) {
+ tabView = createDefaultTabView(getContext());
+ }
+
+ if (tabTitleView == null && TextView.class.isInstance(tabView)) {
+ tabTitleView = (TextView) tabView;
+ }
+
+ tabTitleView.setText(adapter.getPageTitle(i));
+ tabView.setOnClickListener(tabClickListener);
+
+ mTabStrip.addView(tabView);
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (mViewPager != null) {
+ scrollToTab(mViewPager.getCurrentItem(), 0);
+ }
+ }
+
+ private void scrollToTab(int tabIndex, int positionOffset) {
+ final int tabStripChildCount = mTabStrip.getChildCount();
+ if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) {
+ return;
+ }
+
+ View selectedChild = mTabStrip.getChildAt(tabIndex);
+ if (selectedChild != null) {
+ int targetScrollX = selectedChild.getLeft() + positionOffset;
+
+ if (tabIndex > 0 || positionOffset > 0) {
+ // If we're not at the first child and are mid-scroll, make sure we obey the offset
+ targetScrollX -= mTitleOffset;
+ }
+
+ scrollTo(targetScrollX, 0);
+ }
+ }
+
+ private class InternalViewPagerListener implements ViewPager.OnPageChangeListener {
+ private int mScrollState;
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ int tabStripChildCount = mTabStrip.getChildCount();
+ if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
+ return;
+ }
+
+ mTabStrip.onViewPagerPageChanged(position, positionOffset);
+
+ View selectedTitle = mTabStrip.getChildAt(position);
+ int extraOffset = (selectedTitle != null)
+ ? (int) (positionOffset * selectedTitle.getWidth())
+ : 0;
+ scrollToTab(position, extraOffset);
+
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageScrolled(position, positionOffset,
+ positionOffsetPixels);
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ mScrollState = state;
+
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageScrollStateChanged(state);
+ }
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
+ mTabStrip.onViewPagerPageChanged(position, 0f);
+ scrollToTab(position, 0);
+ }
+
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageSelected(position);
+ }
+ }
+
+ }
+
+ private class TabClickListener implements View.OnClickListener {
+ @Override
+ public void onClick(View v) {
+ for (int i = 0; i < mTabStrip.getChildCount(); i++) {
+ if (v == mTabStrip.getChildAt(i)) {
+ mViewPager.setCurrentItem(i);
+ return;
+ }
+ }
+ }
+ }
+
+}
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/view/SlidingTabStrip.java b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/view/SlidingTabStrip.java
new file mode 100644
index 0000000..d5bbbae
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/common/view/SlidingTabStrip.java
@@ -0,0 +1,208 @@
+/*
+ * 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 com.example.android.common.view;
+
+import android.R;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.LinearLayout;
+
+class SlidingTabStrip extends LinearLayout {
+
+ private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 2;
+ private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26;
+ private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 8;
+ private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5;
+
+ private static final int DEFAULT_DIVIDER_THICKNESS_DIPS = 1;
+ private static final byte DEFAULT_DIVIDER_COLOR_ALPHA = 0x20;
+ private static final float DEFAULT_DIVIDER_HEIGHT = 0.5f;
+
+ private final int mBottomBorderThickness;
+ private final Paint mBottomBorderPaint;
+
+ private final int mSelectedIndicatorThickness;
+ private final Paint mSelectedIndicatorPaint;
+
+ private final int mDefaultBottomBorderColor;
+
+ private final Paint mDividerPaint;
+ private final float mDividerHeight;
+
+ private int mSelectedPosition;
+ private float mSelectionOffset;
+
+ private SlidingTabLayout.TabColorizer mCustomTabColorizer;
+ private final SimpleTabColorizer mDefaultTabColorizer;
+
+ SlidingTabStrip(Context context) {
+ this(context, null);
+ }
+
+ SlidingTabStrip(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setWillNotDraw(false);
+
+ final float density = getResources().getDisplayMetrics().density;
+
+ TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.colorForeground, outValue, true);
+ final int themeForegroundColor = outValue.data;
+
+ mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor,
+ DEFAULT_BOTTOM_BORDER_COLOR_ALPHA);
+
+ mDefaultTabColorizer = new SimpleTabColorizer();
+ mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR);
+ mDefaultTabColorizer.setDividerColors(setColorAlpha(themeForegroundColor,
+ DEFAULT_DIVIDER_COLOR_ALPHA));
+
+ mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density);
+ mBottomBorderPaint = new Paint();
+ mBottomBorderPaint.setColor(mDefaultBottomBorderColor);
+
+ mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density);
+ mSelectedIndicatorPaint = new Paint();
+
+ mDividerHeight = DEFAULT_DIVIDER_HEIGHT;
+ mDividerPaint = new Paint();
+ mDividerPaint.setStrokeWidth((int) (DEFAULT_DIVIDER_THICKNESS_DIPS * density));
+ }
+
+ void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) {
+ mCustomTabColorizer = customTabColorizer;
+ invalidate();
+ }
+
+ void setSelectedIndicatorColors(int... colors) {
+ // Make sure that the custom colorizer is removed
+ mCustomTabColorizer = null;
+ mDefaultTabColorizer.setIndicatorColors(colors);
+ invalidate();
+ }
+
+ void setDividerColors(int... colors) {
+ // Make sure that the custom colorizer is removed
+ mCustomTabColorizer = null;
+ mDefaultTabColorizer.setDividerColors(colors);
+ invalidate();
+ }
+
+ void onViewPagerPageChanged(int position, float positionOffset) {
+ mSelectedPosition = position;
+ mSelectionOffset = positionOffset;
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ final int height = getHeight();
+ final int childCount = getChildCount();
+ final int dividerHeightPx = (int) (Math.min(Math.max(0f, mDividerHeight), 1f) * height);
+ final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null
+ ? mCustomTabColorizer
+ : mDefaultTabColorizer;
+
+ // Thick colored underline below the current selection
+ if (childCount > 0) {
+ View selectedTitle = getChildAt(mSelectedPosition);
+ int left = selectedTitle.getLeft();
+ int right = selectedTitle.getRight();
+ int color = tabColorizer.getIndicatorColor(mSelectedPosition);
+
+ if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
+ int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1);
+ if (color != nextColor) {
+ color = blendColors(nextColor, color, mSelectionOffset);
+ }
+
+ // Draw the selection partway between the tabs
+ View nextTitle = getChildAt(mSelectedPosition + 1);
+ left = (int) (mSelectionOffset * nextTitle.getLeft() +
+ (1.0f - mSelectionOffset) * left);
+ right = (int) (mSelectionOffset * nextTitle.getRight() +
+ (1.0f - mSelectionOffset) * right);
+ }
+
+ mSelectedIndicatorPaint.setColor(color);
+
+ canvas.drawRect(left, height - mSelectedIndicatorThickness, right,
+ height, mSelectedIndicatorPaint);
+ }
+
+ // Thin underline along the entire bottom edge
+ canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint);
+
+ // Vertical separators between the titles
+ int separatorTop = (height - dividerHeightPx) / 2;
+ for (int i = 0; i < childCount - 1; i++) {
+ View child = getChildAt(i);
+ mDividerPaint.setColor(tabColorizer.getDividerColor(i));
+ canvas.drawLine(child.getRight(), separatorTop, child.getRight(),
+ separatorTop + dividerHeightPx, mDividerPaint);
+ }
+ }
+
+ /**
+ * Set the alpha value of the {@code color} to be the given {@code alpha} value.
+ */
+ private static int setColorAlpha(int color, byte alpha) {
+ return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ /**
+ * Blend {@code color1} and {@code color2} using the given ratio.
+ *
+ * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend,
+ * 0.0 will return {@code color2}.
+ */
+ private static int blendColors(int color1, int color2, float ratio) {
+ final float inverseRation = 1f - ratio;
+ float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation);
+ float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation);
+ float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation);
+ return Color.rgb((int) r, (int) g, (int) b);
+ }
+
+ private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer {
+ private int[] mIndicatorColors;
+ private int[] mDividerColors;
+
+ @Override
+ public final int getIndicatorColor(int position) {
+ return mIndicatorColors[position % mIndicatorColors.length];
+ }
+
+ @Override
+ public final int getDividerColor(int position) {
+ return mDividerColors[position % mDividerColors.length];
+ }
+
+ void setIndicatorColors(int... colors) {
+ mIndicatorColors = colors;
+ }
+
+ void setDividerColors(int... colors) {
+ mDividerColors = colors;
+ }
+ }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_action_grid.png b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_action_grid.png
new file mode 100644
index 0000000..e04f4a7
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_action_grid.png
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_action_list.png b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_action_list.png
new file mode 100644
index 0000000..4131dba
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_action_list.png
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_launcher.png b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..b7a67c0
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/tile.9.png b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/tile.9.png
new file mode 100644
index 0000000..1358628
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/tile.9.png
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_action_grid.png b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_action_grid.png
new file mode 100644
index 0000000..f2a83e3
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_action_grid.png
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_action_list.png b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_action_list.png
new file mode 100644
index 0000000..e248a48
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_action_list.png
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_launcher.png b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..1c9fc09
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p1.jpg b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p1.jpg
new file mode 100644
index 0000000..10f07ac
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p1.jpg
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p10.jpg b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p10.jpg
new file mode 100644
index 0000000..4272f4c
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p10.jpg
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p11.jpg b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p11.jpg
new file mode 100644
index 0000000..c5722b2
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p11.jpg
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p2.jpg b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p2.jpg
new file mode 100644
index 0000000..ca380ae
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p2.jpg
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p3.jpg b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p3.jpg
new file mode 100644
index 0000000..6fc71e7
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p3.jpg
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p4.jpg b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p4.jpg
new file mode 100644
index 0000000..153c1ff
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p4.jpg
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p5.jpg b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p5.jpg
new file mode 100644
index 0000000..46d6a13
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p5.jpg
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p6.jpg b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p6.jpg
new file mode 100644
index 0000000..89ccb83
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p6.jpg
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p7.jpg b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p7.jpg
new file mode 100644
index 0000000..7e9546d
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p7.jpg
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p8.jpg b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p8.jpg
new file mode 100644
index 0000000..21e25ba
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p8.jpg
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p9.jpg b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p9.jpg
new file mode 100644
index 0000000..79854cb
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p9.jpg
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_action_grid.png b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_action_grid.png
new file mode 100644
index 0000000..ecd39b5
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_action_grid.png
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_action_list.png b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_action_list.png
new file mode 100644
index 0000000..e7e510d
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_action_list.png
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_launcher.png b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..11b9928
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_action_grid.png b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_action_grid.png
new file mode 100644
index 0000000..3ba98fc
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_action_grid.png
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_action_list.png b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_action_list.png
new file mode 100644
index 0000000..d187732
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_action_list.png
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_launcher.png b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..f136c9f
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout-w720dp/activity_main.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout-w720dp/activity_main.xml
new file mode 100755
index 0000000..c9a52f6
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout-w720dp/activity_main.xml
@@ -0,0 +1,73 @@
+<!--
+ Copyright 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/sample_main_layout">
+
+ <LinearLayout
+ android:id="@+id/sample_output"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <FrameLayout
+ style="@style/Widget.SampleMessageTile"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ style="@style/Widget.SampleMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/margin_medium"
+ android:paddingRight="@dimen/margin_medium"
+ android:paddingTop="@dimen/margin_large"
+ android:paddingBottom="@dimen/margin_large"
+ android:text="@string/intro_message" />
+ </FrameLayout>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@android:color/darker_gray" />
+
+ <fragment
+ android:name="com.example.android.common.logger.LogFragment"
+ android:id="@+id/log_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1" />
+
+ </LinearLayout>
+
+ <View
+ android:layout_width="1dp"
+ android:layout_height="match_parent"
+ android:background="@android:color/darker_gray" />
+
+ <FrameLayout
+ android:id="@+id/sample_content_fragment"
+ android:layout_weight="2"
+ android:layout_width="0px"
+ android:layout_height="match_parent" />
+
+</LinearLayout>
+
+
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/activity_main.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/activity_main.xml
new file mode 100755
index 0000000..1ae4f98
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,65 @@
+<!--
+ Copyright 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/sample_main_layout">
+
+ <ViewAnimator
+ android:id="@+id/sample_output"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1">
+
+ <ScrollView
+ style="@style/Widget.SampleMessageTile"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ style="@style/Widget.SampleMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/horizontal_page_margin"
+ android:paddingRight="@dimen/horizontal_page_margin"
+ android:paddingTop="@dimen/vertical_page_margin"
+ android:paddingBottom="@dimen/vertical_page_margin"
+ android:text="@string/intro_message" />
+ </ScrollView>
+
+ <fragment
+ android:name="com.example.android.common.logger.LogFragment"
+ android:id="@+id/log_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </ViewAnimator>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@android:color/darker_gray" />
+
+ <FrameLayout
+ android:id="@+id/sample_content_fragment"
+ android:layout_weight="2"
+ android:layout_width="match_parent"
+ android:layout_height="0px" />
+
+</LinearLayout>
+
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_adapter_transition.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_adapter_transition.xml
new file mode 100644
index 0000000..22ec090
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_adapter_transition.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2014 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context="com.example.android.adaptertransition.AdapterTransitionFragment">
+
+ <FrameLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ <FrameLayout
+ android:id="@+id/cover"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#f3f3f3"
+ android:visibility="invisible"/>
+
+</FrameLayout>
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_meat_grid.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_meat_grid.xml
new file mode 100644
index 0000000..9a4f7a1
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_meat_grid.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2014 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.
+-->
+<GridView
+ android:id="@+id/abs_list_view"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ android:columnWidth="150dp"
+ android:horizontalSpacing="1dp"
+ android:numColumns="auto_fit"
+ android:padding="1dp"
+ android:scrollbars="none"
+ android:stretchMode="columnWidth"
+ android:verticalSpacing="1dp"/>
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_meat_list.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_meat_list.xml
new file mode 100644
index 0000000..4523b26
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_meat_list.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2014 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.
+-->
+<ListView
+ android:id="@+id/abs_list_view"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/item_meat_grid.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/item_meat_grid.xml
new file mode 100644
index 0000000..d7fb77a
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/item_meat_grid.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2014 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.
+-->
+<RelativeLayout
+ android:id="@+id/meat_container"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="150dp">
+
+ <ImageView
+ android:id="@+id/meat_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop"
+ tools:src="@drawable/p1"/>
+
+ <TextView
+ android:id="@+id/meat_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:layout_gravity="bottom|end"
+ android:layout_marginEnd="16dp"
+ android:layout_marginStart="16dp"
+ android:gravity="center_horizontal"
+ android:shadowColor="#000000"
+ android:shadowDx="0"
+ android:shadowDy="0"
+ android:shadowRadius="10"
+ android:textColor="#ffffff"
+ android:textSize="24sp"
+ android:textStyle="bold"
+ tools:text="Hello"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/item_meat_list.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/item_meat_list.xml
new file mode 100644
index 0000000..8d75b90
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/layout/item_meat_list.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2014 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.
+-->
+<RelativeLayout
+ android:id="@+id/meat_container"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+ <ImageView
+ android:id="@+id/meat_image"
+ android:layout_width="64dp"
+ android:layout_height="64dp"
+ android:scaleType="centerCrop"
+ tools:src="@drawable/p1"/>
+
+ <TextView
+ android:id="@+id/meat_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
+ android:layout_toEndOf="@id/meat_image"
+ android:layout_centerInParent="true"
+ android:gravity="center_vertical"
+ android:textSize="24sp"
+ tools:text="Title"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/menu/fragment_adapter_transition.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/menu/fragment_adapter_transition.xml
new file mode 100644
index 0000000..2a51b11
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/menu/fragment_adapter_transition.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2014 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@+id/action_toggle"
+ android:icon="@drawable/ic_action_grid"
+ android:showAsAction="always"
+ android:title="Toggle view"/>
+</menu>
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/menu/main.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/menu/main.xml
new file mode 100644
index 0000000..b49c2c5
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/menu/main.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright 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.
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/menu_toggle_log"
+ android:showAsAction="always"
+ android:title="@string/sample_show_log" />
+</menu>
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values-sw600dp/template-dimens.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values-sw600dp/template-dimens.xml
new file mode 100644
index 0000000..22074a2
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values-sw600dp/template-dimens.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <!-- Semantic definitions -->
+
+ <dimen name="horizontal_page_margin">@dimen/margin_huge</dimen>
+ <dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
+
+</resources>
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values-sw600dp/template-styles.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values-sw600dp/template-styles.xml
new file mode 100644
index 0000000..03d1974
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values-sw600dp/template-styles.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <style name="Widget.SampleMessage">
+ <item name="android:textAppearance">?android:textAppearanceLarge</item>
+ <item name="android:lineSpacingMultiplier">1.2</item>
+ <item name="android:shadowDy">-6.5</item>
+ </style>
+
+</resources>
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values-w820dp/dimens.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/base-strings.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/base-strings.xml
new file mode 100644
index 0000000..353c67e
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/base-strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 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.
+-->
+
+
+
+<resources>
+ <string name="app_name">AdapterTransition</string>
+ <string name="intro_message">
+ <![CDATA[
+
+
+ Transition cannot be directly applied to AdapterViews. In this sample, we demonstrate how to create a overlay layout and run a Transition on it.
+
+
+ ]]>
+ </string>
+</resources>
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/dimens.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..a0171a7
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+
+ </resources>
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/strings.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/strings.xml
new file mode 100755
index 0000000..7b9d9ec
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+ Copyright 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.
+-->
+<resources>
+ <string name="sample_show_log">Show Log</string>
+ <string name="sample_hide_log">Hide Log</string>
+</resources>
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/template-dimens.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/template-dimens.xml
new file mode 100644
index 0000000..39e710b
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/template-dimens.xml
@@ -0,0 +1,32 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <!-- Define standard dimensions to comply with Holo-style grids and rhythm. -->
+
+ <dimen name="margin_tiny">4dp</dimen>
+ <dimen name="margin_small">8dp</dimen>
+ <dimen name="margin_medium">16dp</dimen>
+ <dimen name="margin_large">32dp</dimen>
+ <dimen name="margin_huge">64dp</dimen>
+
+ <!-- Semantic definitions -->
+
+ <dimen name="horizontal_page_margin">@dimen/margin_medium</dimen>
+ <dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
+
+</resources>
diff --git a/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/template-styles.xml b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/template-styles.xml
new file mode 100644
index 0000000..404623e
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/AdapterTransitionSample/src/main/res/values/template-styles.xml
@@ -0,0 +1,42 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <!-- Activity themes -->
+
+ <style name="Theme.Base" parent="android:Theme.Holo.Light" />
+
+ <style name="Theme.Sample" parent="Theme.Base" />
+
+ <style name="AppTheme" parent="Theme.Sample" />
+ <!-- Widget styling -->
+
+ <style name="Widget" />
+
+ <style name="Widget.SampleMessage">
+ <item name="android:textAppearance">?android:textAppearanceMedium</item>
+ <item name="android:lineSpacingMultiplier">1.1</item>
+ </style>
+
+ <style name="Widget.SampleMessageTile">
+ <item name="android:background">@drawable/tile</item>
+ <item name="android:shadowColor">#7F000000</item>
+ <item name="android:shadowDy">-3.5</item>
+ <item name="android:shadowRadius">2</item>
+ </style>
+
+</resources>
diff --git a/prebuilts/gradle/AdapterTransition/README.txt b/prebuilts/gradle/AdapterTransition/README.txt
new file mode 100644
index 0000000..9616c58
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/README.txt
@@ -0,0 +1,18 @@
+Build Instructions
+-------------------
+
+This sample uses the Gradle build system. To build this project, use the
+"gradlew build" command or use "Import Project" in Android Studio.
+
+To see a list of all available commands, run "gradlew tasks".
+
+Dependencies
+-------------
+
+- Android SDK Build-tools v18.1
+- Android Support Repository v2
+
+Dependencies are available for download via the Android SDK Manager.
+
+Android Studio is available for download at:
+ http://developer.android.com/sdk/installing/studio.html
diff --git a/prebuilts/gradle/AdapterTransition/build.gradle b/prebuilts/gradle/AdapterTransition/build.gradle
new file mode 100644
index 0000000..584ba87
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/build.gradle
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/prebuilts/gradle/AdapterTransition/gradle/wrapper/gradle-wrapper.jar b/prebuilts/gradle/AdapterTransition/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/prebuilts/gradle/AdapterTransition/gradle/wrapper/gradle-wrapper.properties b/prebuilts/gradle/AdapterTransition/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..4f5d419
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http://services.gradle.org/distributions/gradle-1.10-bin.zip
diff --git a/prebuilts/gradle/AdapterTransition/gradlew b/prebuilts/gradle/AdapterTransition/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/prebuilts/gradle/AdapterTransition/gradlew.bat b/prebuilts/gradle/AdapterTransition/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/prebuilts/gradle/AdapterTransition/settings.gradle b/prebuilts/gradle/AdapterTransition/settings.gradle
new file mode 100644
index 0000000..3284a23
--- /dev/null
+++ b/prebuilts/gradle/AdapterTransition/settings.gradle
@@ -0,0 +1,4 @@
+
+
+
+include 'AdapterTransitionSample'
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/build.gradle b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/build.gradle
index 5681239..501d7fe 100644
--- a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/build.gradle
+++ b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/build.gradle
@@ -14,8 +14,11 @@
apply plugin: 'android'
dependencies {
- // Add the support lib that is appropriate for SDK 4
+
+ // Add the support lib that is appropriate for SDK 4
compile "com.android.support:support-v4:19.0.+"
+
+
}
// The sample build uses multiple directories to
@@ -28,6 +31,7 @@
android {
compileSdkVersion 19
+
buildToolsVersion "19.0.1"
sourceSets {
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/java/com/example/android/advancedimmersivemode/AdvancedImmersiveModeFragment.java b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/java/com/example/android/advancedimmersivemode/AdvancedImmersiveModeFragment.java
index fe11ecb..d8fb0d4 100644
--- a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/java/com/example/android/advancedimmersivemode/AdvancedImmersiveModeFragment.java
+++ b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/java/com/example/android/advancedimmersivemode/AdvancedImmersiveModeFragment.java
@@ -17,9 +17,10 @@
import android.os.Bundle;
import android.support.v4.app.Fragment;
-import android.view.MenuItem;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Button;
import android.widget.CheckBox;
import com.example.android.common.logger.Log;
@@ -46,49 +47,136 @@
}
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
+ final View flagsView = inflater.inflate(R.layout.fragment_flags, container, false);
+ mLowProfileCheckBox = (CheckBox) flagsView.findViewById(R.id.flag_enable_lowprof);
+ mHideNavCheckbox = (CheckBox) flagsView.findViewById(R.id.flag_hide_navbar);
+ mHideStatusBarCheckBox = (CheckBox) flagsView.findViewById(R.id.flag_hide_statbar);
+ mImmersiveModeCheckBox = (CheckBox) flagsView.findViewById(R.id.flag_enable_immersive);
+ mImmersiveModeStickyCheckBox =
+ (CheckBox) flagsView.findViewById(R.id.flag_enable_immersive_sticky);
- final View decorView = getActivity().getWindow().getDecorView();
- ViewGroup parentView = (ViewGroup) getActivity().getWindow().getDecorView()
- .findViewById(R.id.sample_main_layout);
+ Button toggleFlagsButton = (Button) flagsView.findViewById(R.id.btn_changeFlags);
+ toggleFlagsButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ toggleUiFlags();
+ }
+ });
- mLowProfileCheckBox = new CheckBox(getActivity());
- mLowProfileCheckBox.setText("Enable Low Profile mode.");
- parentView.addView(mLowProfileCheckBox);
+ Button presetsImmersiveModeButton = (Button) flagsView.findViewById(R.id.btn_immersive);
+ presetsImmersiveModeButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
- mHideNavCheckbox = new CheckBox(getActivity());
- mHideNavCheckbox.setChecked(true);
- mHideNavCheckbox.setText("Hide Navigation bar");
- parentView.addView(mHideNavCheckbox);
+ // BEGIN_INCLUDE(immersive_presets)
+ // For immersive mode, the FULLSCREEN, HIDE_HAVIGATION and IMMERSIVE
+ // flags should be set (you can use IMMERSIVE_STICKY instead of IMMERSIVE
+ // as appropriate for your app). The LOW_PROFILE flag should be cleared.
- mHideStatusBarCheckBox = new CheckBox(getActivity());
- mHideStatusBarCheckBox.setChecked(true);
- mHideStatusBarCheckBox.setText("Hide Status Bar");
- parentView.addView(mHideStatusBarCheckBox);
+ // Immersive mode is primarily for situations where the user will be
+ // interacting with the screen, like games or reading books.
+ int uiOptions = flagsView.getSystemUiVisibility();
+ uiOptions &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE;
+ uiOptions |= View.SYSTEM_UI_FLAG_FULLSCREEN;
+ uiOptions |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+ uiOptions |= View.SYSTEM_UI_FLAG_IMMERSIVE;
+ uiOptions &= ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ flagsView.setSystemUiVisibility(uiOptions);
+ // END_INCLUDE(immersive_presets)
- mImmersiveModeCheckBox = new CheckBox(getActivity());
- mImmersiveModeCheckBox.setText("Enable Immersive Mode.");
- parentView.addView(mImmersiveModeCheckBox);
+ dumpFlagStateToLog(uiOptions);
- mImmersiveModeStickyCheckBox = new CheckBox(getActivity());
- mImmersiveModeStickyCheckBox.setText("Enable Immersive Mode (Sticky)");
- parentView.addView(mImmersiveModeStickyCheckBox);
+ // The below code just updates the checkboxes to reflect which flags have been set.
+ mLowProfileCheckBox.setChecked(false);
+ mHideNavCheckbox.setChecked(true);
+ mHideStatusBarCheckBox.setChecked(true);
+ mImmersiveModeCheckBox.setChecked(true);
+ mImmersiveModeStickyCheckBox.setChecked(false);
+ }
+ });
+
+ Button presetsLeanbackModeButton = (Button) flagsView.findViewById(R.id.btn_leanback);
+ presetsLeanbackModeButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // BEGIN_INCLUDE(leanback_presets)
+ // For leanback mode, only the HIDE_NAVE and HIDE_STATUSBAR flags
+ // should be checked. In this case IMMERSIVE should *not* be set,
+ // since this mode is left as soon as the user touches the screen.
+ int uiOptions = flagsView.getSystemUiVisibility();
+ uiOptions &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE;
+ uiOptions |= View.SYSTEM_UI_FLAG_FULLSCREEN;
+ uiOptions |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+ uiOptions &= ~View.SYSTEM_UI_FLAG_IMMERSIVE;
+ uiOptions &= ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ flagsView.setSystemUiVisibility(uiOptions);
+ // END_INCLUDE(leanback_presets)
+
+ dumpFlagStateToLog(uiOptions);
+
+ // The below code just updates the checkboxes to reflect which flags have been set.
+ mLowProfileCheckBox.setChecked(false);
+ mHideNavCheckbox.setChecked(true);
+ mHideStatusBarCheckBox.setChecked(true);
+ mImmersiveModeCheckBox.setChecked(false);
+ mImmersiveModeStickyCheckBox.setChecked(false);
+ }
+ });
+
+ // Setting these flags makes the content appear under the navigation
+ // bars, so that showing/hiding the nav bars doesn't resize the content
+ // window, which can be jarring.
+ int uiOptions = flagsView.getSystemUiVisibility();
+ uiOptions |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+ uiOptions |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+ uiOptions |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ flagsView.setSystemUiVisibility(uiOptions);
+
+ return flagsView;
}
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == R.id.sample_action) {
- toggleImmersiveMode();
+ /**
+ * Helper method to dump flag state to the log.
+ * @param uiFlags Set of UI flags to inspect
+ */
+ public void dumpFlagStateToLog(int uiFlags) {
+ if ((uiFlags & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
+ Log.i(TAG, "SYSTEM_UI_FLAG_LOW_PROFILE is set");
+ } else {
+ Log.i(TAG, "SYSTEM_UI_FLAG_LOW_PROFILE is unset");
}
- return true;
+
+ if ((uiFlags & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0) {
+ Log.i(TAG, "SYSTEM_UI_FLAG_FULLSCREEN is set");
+ } else {
+ Log.i(TAG, "SYSTEM_UI_FLAG_FULLSCREEN is unset");
+ }
+
+ if ((uiFlags & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0) {
+ Log.i(TAG, "SYSTEM_UI_FLAG_HIDE_NAVIGATION is set");
+ } else {
+ Log.i(TAG, "SYSTEM_UI_FLAG_HIDE_NAVIGATION is unset");
+ }
+
+ if ((uiFlags & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0) {
+ Log.i(TAG, "SYSTEM_UI_FLAG_IMMERSIVE is set");
+ } else {
+ Log.i(TAG, "SYSTEM_UI_FLAG_IMMERSIVE is unset");
+ }
+
+ if ((uiFlags & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0) {
+ Log.i(TAG, "SYSTEM_UI_FLAG_IMMERSIVE_STICKY is set");
+ } else {
+ Log.i(TAG, "SYSTEM_UI_FLAG_IMMERSIVE_STICKY is unset");
+ }
}
/**
* Detects and toggles immersive mode (also known as "hidey bar" mode).
*/
- public void toggleImmersiveMode() {
+ public void toggleUiFlags() {
// BEGIN_INCLUDE (get_current_ui_flags)
// The "Decor View" is the parent view of the Activity. It's also conveniently the easiest
@@ -168,7 +256,8 @@
// BEGIN_INCLUDE (set_ui_flags)
//Set the new UI flags.
decorView.setSystemUiVisibility(newUiOptions);
- Log.i(TAG, "Current height: " + decorView.getHeight() + ", width: " + decorView.getWidth());
// END_INCLUDE (set_ui_flags)
+
+ dumpFlagStateToLog(uiOptions);
}
}
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/java/com/example/android/advancedimmersivemode/MainActivity.java b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/java/com/example/android/advancedimmersivemode/MainActivity.java
index 0ebe878..e323557 100644
--- a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/java/com/example/android/advancedimmersivemode/MainActivity.java
+++ b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/java/com/example/android/advancedimmersivemode/MainActivity.java
@@ -22,6 +22,8 @@
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.ViewAnimator;
import com.example.android.common.activities.SampleActivityBase;
import com.example.android.common.logger.Log;
@@ -30,26 +32,28 @@
import com.example.android.common.logger.MessageOnlyLogFilter;
/**
- * A simple launcher activity containing a summary sample description
- * and a few action bar buttons.
+ * A simple launcher activity containing a summary sample description, sample log and a custom
+ * {@link android.support.v4.app.Fragment} which can display a view.
+ * <p>
+ * For devices with displays with a width of 720dp or greater, the sample log is always visible,
+ * on other devices it's visibility is controlled by an item on the Action Bar.
*/
public class MainActivity extends SampleActivityBase {
public static final String TAG = "MainActivity";
- public static final String FRAGTAG = "AdvancedImmersiveModeFragment";
+ // Whether the Log Fragment is currently shown
+ private boolean mLogShown;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- if (getSupportFragmentManager().findFragmentByTag(FRAGTAG) == null ) {
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- AdvancedImmersiveModeFragment fragment = new AdvancedImmersiveModeFragment();
- transaction.add(fragment, FRAGTAG);
- transaction.commit();
- }
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ AdvancedImmersiveModeFragment fragment = new AdvancedImmersiveModeFragment();
+ transaction.replace(R.id.sample_content_fragment, fragment);
+ transaction.commit();
}
@Override
@@ -58,6 +62,32 @@
return true;
}
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem logToggle = menu.findItem(R.id.menu_toggle_log);
+ logToggle.setVisible(findViewById(R.id.sample_output) instanceof ViewAnimator);
+ logToggle.setTitle(mLogShown ? R.string.sample_hide_log : R.string.sample_show_log);
+
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case R.id.menu_toggle_log:
+ mLogShown = !mLogShown;
+ ViewAnimator output = (ViewAnimator) findViewById(R.id.sample_output);
+ if (mLogShown) {
+ output.setDisplayedChild(1);
+ } else {
+ output.setDisplayedChild(0);
+ }
+ supportInvalidateOptionsMenu();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
/** Create a chain of targets that will receive log data */
@Override
public void initializeLogging() {
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-hdpi/ic_launcher.png b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-hdpi/ic_launcher.png
index b1efaf4..b96e6a5 100644
--- a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-hdpi/ic_launcher.png
+++ b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-mdpi/ic_launcher.png b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-mdpi/ic_launcher.png
index f5f9244..1294d5b 100644
--- a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-mdpi/ic_launcher.png
+++ b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xhdpi/ic_launcher.png b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xhdpi/ic_launcher.png
index 5d07b3f..9f101ff 100644
--- a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xhdpi/ic_launcher.png
+++ b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xxhdpi/ic_launcher.png b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xxhdpi/ic_launcher.png
index 6ef21e1..7a195a1 100644
--- a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xxhdpi/ic_launcher.png
+++ b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/layout/activity_main.xml b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/layout/activity_main.xml
index bc5a575..1ae4f98 100755
--- a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/layout/activity_main.xml
+++ b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/layout/activity_main.xml
@@ -14,25 +14,52 @@
limitations under the License.
-->
<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:id="@+id/sample_main_layout">
- <TextView android:id="@+id/sample_output"
- style="@style/Widget.SampleMessage"
- android:layout_weight="1"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/sample_main_layout">
+
+ <ViewAnimator
+ android:id="@+id/sample_output"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1">
+
+ <ScrollView
+ style="@style/Widget.SampleMessageTile"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:text="@string/intro_message" />
+ android:layout_height="match_parent">
+
+ <TextView
+ style="@style/Widget.SampleMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/horizontal_page_margin"
+ android:paddingRight="@dimen/horizontal_page_margin"
+ android:paddingTop="@dimen/vertical_page_margin"
+ android:paddingBottom="@dimen/vertical_page_margin"
+ android:text="@string/intro_message" />
+ </ScrollView>
+
+ <fragment
+ android:name="com.example.android.common.logger.LogFragment"
+ android:id="@+id/log_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </ViewAnimator>
+
<View
- android:layout_width="fill_parent"
- android:layout_height="1dp"
- android:background="@android:color/darker_gray"/>
- <fragment
- android:name="com.example.android.common.logger.LogFragment"
- android:id="@+id/log_fragment"
- android:layout_weight="1"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@android:color/darker_gray" />
+
+ <FrameLayout
+ android:id="@+id/sample_content_fragment"
+ android:layout_weight="2"
+ android:layout_width="match_parent"
+ android:layout_height="0px" />
+
</LinearLayout>
+
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/menu/main.xml b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/menu/main.xml
index 2c3515d..b49c2c5 100644
--- a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/menu/main.xml
+++ b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/menu/main.xml
@@ -15,7 +15,7 @@
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@+id/sample_action"
- android:showAsAction="ifRoom|withText"
- android:title="@string/sample_action" />
+ <item android:id="@+id/menu_toggle_log"
+ android:showAsAction="always"
+ android:title="@string/sample_show_log" />
</menu>
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/values/base-strings.xml b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/values/base-strings.xml
index 305e12a..8e4c710 100644
--- a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/values/base-strings.xml
+++ b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/values/base-strings.xml
@@ -23,13 +23,10 @@
<![CDATA[
- \"Immersive Mode\" is a new UI mode which improves \"hide full screen\" and
+ \n\n\n\"Immersive Mode\", added in Android 4.4, improves the \"hide full screen\" and
\"hide nav bar\" modes, by letting users swipe the bars in and out. This sample
- lets the user experiment with immersive mode by enabling it and seeing how it interacts
+ lets the user experiment with immersive mode by seeing how it interacts
with some of the other UI flags related to full-screen apps.
- \n\nThis sample also lets the user choose between normal immersive mode and "sticky"
- immersive mode, which removes the status bar and nav bar
- a few seconds after the user has swiped them back in.
]]>
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/values/strings.xml b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/values/strings.xml
index a65b891..7b9d9ec 100755
--- a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/values/strings.xml
+++ b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/values/strings.xml
@@ -1,22 +1,19 @@
-<?xml version="1.0" encoding="UTF-8"?>
<!--
- Copyright 2013 The Android Open Source Project
+ Copyright 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
+ 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
+ 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.
+ 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.
-->
-
-
-
<resources>
- <string name="sample_action">Try these settings!</string>
+ <string name="sample_show_log">Show Log</string>
+ <string name="sample_hide_log">Hide Log</string>
</resources>
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/values/template-styles.xml b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/values/template-styles.xml
index d3f82ff..404623e 100644
--- a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/values/template-styles.xml
+++ b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/values/template-styles.xml
@@ -20,7 +20,9 @@
<style name="Theme.Base" parent="android:Theme.Holo.Light" />
- <style name="AppTheme" parent="Theme.Base" />
+ <style name="Theme.Sample" parent="Theme.Base" />
+
+ <style name="AppTheme" parent="Theme.Sample" />
<!-- Widget styling -->
<style name="Widget" />
@@ -37,15 +39,4 @@
<item name="android:shadowRadius">2</item>
</style>
-
- <style name="Widget.SampleOutput">
- <item name="android:padding">@dimen/margin_medium</item>
- <item name="android:textAppearance">?android:textAppearanceMedium</item>
- <item name="android:lineSpacingMultiplier">1.1</item>
- </style>
-
- <style name="Log" parent="Widget.SampleOutput">
- <item name="android:typeface">monospace</item>
- </style>
-
</resources>
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/tests/src/com/example/android/advancedimmersivemode/tests/SampleTests.java b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/tests/src/com/example/android/advancedimmersivemode/tests/SampleTests.java
index c51a488..541791a 100644
--- a/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/tests/src/com/example/android/advancedimmersivemode/tests/SampleTests.java
+++ b/prebuilts/gradle/AdvancedImmersiveMode/AdvancedImmersiveModeSample/tests/src/com/example/android/advancedimmersivemode/tests/SampleTests.java
@@ -74,53 +74,8 @@
mTestFragment.mImmersiveModeCheckBox.setChecked(true);
mTestFragment.mHideNavCheckbox.setChecked(true);
mTestFragment.mHideStatusBarCheckBox.setChecked(true);
- mTestFragment.toggleImmersiveMode();
+ mTestFragment.toggleUiFlags();
int newUiFlags = getActivity().getWindow().getDecorView().getSystemUiVisibility();
assertTrue("UI Flags didn't toggle.", uiFlags != newUiFlags);
}
-
- /**
- * Verify that the view's height actually changed when the toggle method is called.
- * This should result in a change in height for the DecorView.
- */
- public void testDecorHeightExpanded() {
- // Grab the initial height of the DecorWindow.
- int startingHeight = getActivity().getWindow().getDecorView().getHeight();
-
- // In order to test that this worked: Need to toggle the immersive mode on the UI thread,
- // wait a suitable amount of time (this test goes with 200 ms), then check to see if the
- // height changed.
- try {
- Runnable testRunnable = (new Runnable() {
- public void run() {
- // Toggle immersive mode
- mTestFragment.mImmersiveModeCheckBox.setChecked(true);
- mTestFragment.mHideNavCheckbox.setChecked(true);
- mTestFragment.mHideStatusBarCheckBox.setChecked(true);
- mTestFragment.toggleImmersiveMode();
- synchronized(this) {
- // Notify any thread waiting on this runnable that it can continue
- this.notify();
- }
- }
- });
- synchronized(testRunnable) {
- // Since toggling immersive mode makes changes to the view hierarchy, it needs to run
- // on the UI thread, or crashing will occur.
- mTestActivity.runOnUiThread(testRunnable);
- testRunnable.wait();
-
- }
- synchronized(this) {
- //Wait about 200ms for the change to take place
- wait(200L);
- }
- } catch (Throwable throwable) {
- fail(throwable.getMessage());
- }
-
- int expandedHeight = getActivity().getWindow().getDecorView().getHeight();
- assertTrue("Bars aren't hidden.", expandedHeight != startingHeight);
- }
-
}
\ No newline at end of file
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.jar b/prebuilts/gradle/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.jar
index 8c0fb64..5838598 100644
--- a/prebuilts/gradle/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.jar
+++ b/prebuilts/gradle/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/prebuilts/gradle/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.properties b/prebuilts/gradle/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.properties
index 861eddc..9e133a0 100644
--- a/prebuilts/gradle/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.properties
+++ b/prebuilts/gradle/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Wed Apr 10 15:27:10 PDT 2013
+#Tue Feb 11 09:26:00 PST 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions/gradle-1.8-bin.zip
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/build.gradle b/prebuilts/gradle/BasicTransition/BasicTransitionSample/build.gradle
new file mode 100644
index 0000000..c39392a
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/build.gradle
@@ -0,0 +1,62 @@
+
+
+
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.8.+'
+ }
+}
+
+apply plugin: 'android'
+
+dependencies {
+
+ // Add the support lib that is appropriate for SDK 19
+ compile "com.android.support:support-v13:19.0.+"
+
+
+}
+
+// The sample build uses multiple directories to
+// keep boilerplate and common code separate from
+// the main sample code.
+List<String> dirs = [
+ 'main', // main sample code; look here for the interesting stuff.
+ 'common', // components that are reused by multiple samples
+ 'template'] // boilerplate code that is generated by the sample template process
+
+android {
+ compileSdkVersion 19
+
+ buildToolsVersion "19.0.1"
+
+ sourceSets {
+ main {
+ dirs.each { dir ->
+ java.srcDirs "src/${dir}/java"
+ res.srcDirs "src/${dir}/res"
+ }
+ }
+ instrumentTest.setRoot('tests')
+ instrumentTest.java.srcDirs = ['tests/src']
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/AndroidManifest.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b4698d6
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2014 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.basictransition"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="19" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/basictransition/BasicTransitionFragment.java b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/basictransition/BasicTransitionFragment.java
new file mode 100644
index 0000000..e67603d
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/basictransition/BasicTransitionFragment.java
@@ -0,0 +1,121 @@
+/*
+ * 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 com.example.android.basictransition;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.transition.Scene;
+import android.transition.TransitionInflater;
+import android.transition.TransitionManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RadioGroup;
+
+public class BasicTransitionFragment extends Fragment
+ implements RadioGroup.OnCheckedChangeListener {
+
+ // We transition between these Scenes
+ private Scene mScene1;
+ private Scene mScene2;
+ private Scene mScene3;
+
+ /** A custom TransitionManager */
+ private TransitionManager mTransitionManagerForScene3;
+
+ /** Transitions take place in this ViewGroup. We retain this for the dynamic transition on scene 4. */
+ private ViewGroup mSceneRoot;
+
+ public static BasicTransitionFragment newInstance() {
+ return new BasicTransitionFragment();
+ }
+
+ public BasicTransitionFragment() {
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_basic_transition, container, false);
+ assert view != null;
+ RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.select_scene);
+ radioGroup.setOnCheckedChangeListener(this);
+ mSceneRoot = (ViewGroup) view.findViewById(R.id.scene_root);
+
+ // BEGIN_INCLUDE(instantiation_from_view)
+ // A Scene can be instantiated from a live view hierarchy.
+ mScene1 = new Scene(mSceneRoot, (ViewGroup) mSceneRoot.findViewById(R.id.container));
+ // END_INCLUDE(instantiation_from_view)
+
+ // BEGIN_INCLUDE(instantiation_from_resource)
+ // You can also inflate a generate a Scene from a layout resource file.
+ mScene2 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene2, getActivity());
+ // END_INCLUDE(instantiation_from_resource)
+
+ // Another scene from a layout resource file.
+ mScene3 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene3, getActivity());
+
+ // BEGIN_INCLUDE(custom_transition_manager)
+ // We create a custom TransitionManager for Scene 3, in which ChangeBounds and Fade
+ // take place at the same time.
+ mTransitionManagerForScene3 = TransitionInflater.from(getActivity())
+ .inflateTransitionManager(R.transition.scene3_transition_manager, mSceneRoot);
+ // END_INCLUDE(custom_transition_manager)
+
+ return view;
+ }
+
+ @Override
+ public void onCheckedChanged(RadioGroup group, int checkedId) {
+ switch (checkedId) {
+ case R.id.select_scene_1: {
+ // BEGIN_INCLUDE(transition_simple)
+ // You can start an automatic transition with TransitionManager.go().
+ TransitionManager.go(mScene1);
+ // END_INCLUDE(transition_simple)
+ break;
+ }
+ case R.id.select_scene_2: {
+ TransitionManager.go(mScene2);
+ break;
+ }
+ case R.id.select_scene_3: {
+ // BEGIN_INCLUDE(transition_custom)
+ // You can also start a transition with a custom TransitionManager.
+ mTransitionManagerForScene3.transitionTo(mScene3);
+ // END_INCLUDE(transition_custom)
+ break;
+ }
+ case R.id.select_scene_4: {
+ // BEGIN_INCLUDE(transition_dynamic)
+ // Alternatively, transition can be invoked dynamically without a Scene.
+ // For this, we first call TransitionManager.beginDelayedTransition().
+ TransitionManager.beginDelayedTransition(mSceneRoot);
+ // Then, we can just change view properties as usual.
+ View square = mSceneRoot.findViewById(R.id.transition_square);
+ ViewGroup.LayoutParams params = square.getLayoutParams();
+ int newSize = getResources().getDimensionPixelSize(R.dimen.square_size_expanded);
+ params.width = newSize;
+ params.height = newSize;
+ square.setLayoutParams(params);
+ // END_INCLUDE(transition_dynamic)
+ break;
+ }
+ }
+ }
+
+}
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/basictransition/MainActivity.java b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/basictransition/MainActivity.java
new file mode 100644
index 0000000..1e7c301
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/basictransition/MainActivity.java
@@ -0,0 +1,110 @@
+/*
+* Copyright 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 com.example.android.basictransition;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentTransaction;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.ViewAnimator;
+
+import com.example.android.common.activities.SampleActivityBase;
+import com.example.android.common.logger.Log;
+import com.example.android.common.logger.LogFragment;
+import com.example.android.common.logger.LogWrapper;
+import com.example.android.common.logger.MessageOnlyLogFilter;
+
+/**
+ * A simple launcher activity containing a summary sample description, sample log and a custom
+ * {@link android.support.v4.app.Fragment} which can display a view.
+ * <p>
+ * For devices with displays with a width of 720dp or greater, the sample log is always visible,
+ * on other devices it's visibility is controlled by an item on the Action Bar.
+ */
+public class MainActivity extends SampleActivityBase {
+
+ public static final String TAG = "MainActivity";
+
+ // Whether the Log Fragment is currently shown
+ private boolean mLogShown;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ BasicTransitionFragment fragment = new BasicTransitionFragment();
+ transaction.replace(R.id.sample_content_fragment, fragment);
+ transaction.commit();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem logToggle = menu.findItem(R.id.menu_toggle_log);
+ logToggle.setVisible(findViewById(R.id.sample_output) instanceof ViewAnimator);
+ logToggle.setTitle(mLogShown ? R.string.sample_hide_log : R.string.sample_show_log);
+
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case R.id.menu_toggle_log:
+ mLogShown = !mLogShown;
+ ViewAnimator output = (ViewAnimator) findViewById(R.id.sample_output);
+ if (mLogShown) {
+ output.setDisplayedChild(1);
+ } else {
+ output.setDisplayedChild(0);
+ }
+ supportInvalidateOptionsMenu();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ /** Create a chain of targets that will receive log data */
+ @Override
+ public void initializeLogging() {
+ // Wraps Android's native log framework.
+ LogWrapper logWrapper = new LogWrapper();
+ // Using Log, front-end to the logging chain, emulates android.util.log method signatures.
+ Log.setLogNode(logWrapper);
+
+ // Filter strips out everything except the message text.
+ MessageOnlyLogFilter msgFilter = new MessageOnlyLogFilter();
+ logWrapper.setNext(msgFilter);
+
+ // On screen logging via a fragment with a TextView.
+ LogFragment logFragment = (LogFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.log_fragment);
+ msgFilter.setNext(logFragment.getLogView());
+
+ Log.i(TAG, "Ready");
+ }
+}
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/activities/SampleActivityBase.java b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/activities/SampleActivityBase.java
new file mode 100644
index 0000000..3228927
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/activities/SampleActivityBase.java
@@ -0,0 +1,52 @@
+/*
+* Copyright 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 com.example.android.common.activities;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+
+import com.example.android.common.logger.Log;
+import com.example.android.common.logger.LogWrapper;
+
+/**
+ * Base launcher activity, to handle most of the common plumbing for samples.
+ */
+public class SampleActivityBase extends FragmentActivity {
+
+ public static final String TAG = "SampleActivityBase";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ initializeLogging();
+ }
+
+ /** Set up targets to receive log data */
+ public void initializeLogging() {
+ // Using Log, front-end to the logging chain, emulates android.util.log method signatures.
+ // Wraps Android's native log framework
+ LogWrapper logWrapper = new LogWrapper();
+ Log.setLogNode(logWrapper);
+
+ Log.i(TAG, "Ready");
+ }
+}
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/Log.java b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/Log.java
new file mode 100644
index 0000000..17503c5
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/Log.java
@@ -0,0 +1,236 @@
+/*
+ * 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 com.example.android.common.logger;
+
+/**
+ * Helper class for a list (or tree) of LoggerNodes.
+ *
+ * <p>When this is set as the head of the list,
+ * an instance of it can function as a drop-in replacement for {@link android.util.Log}.
+ * Most of the methods in this class server only to map a method call in Log to its equivalent
+ * in LogNode.</p>
+ */
+public class Log {
+ // Grabbing the native values from Android's native logging facilities,
+ // to make for easy migration and interop.
+ public static final int NONE = -1;
+ public static final int VERBOSE = android.util.Log.VERBOSE;
+ public static final int DEBUG = android.util.Log.DEBUG;
+ public static final int INFO = android.util.Log.INFO;
+ public static final int WARN = android.util.Log.WARN;
+ public static final int ERROR = android.util.Log.ERROR;
+ public static final int ASSERT = android.util.Log.ASSERT;
+
+ // Stores the beginning of the LogNode topology.
+ private static LogNode mLogNode;
+
+ /**
+ * Returns the next LogNode in the linked list.
+ */
+ public static LogNode getLogNode() {
+ return mLogNode;
+ }
+
+ /**
+ * Sets the LogNode data will be sent to.
+ */
+ public static void setLogNode(LogNode node) {
+ mLogNode = node;
+ }
+
+ /**
+ * Instructs the LogNode to print the log data provided. Other LogNodes can
+ * be chained to the end of the LogNode as desired.
+ *
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void println(int priority, String tag, String msg, Throwable tr) {
+ if (mLogNode != null) {
+ mLogNode.println(priority, tag, msg, tr);
+ }
+ }
+
+ /**
+ * Instructs the LogNode to print the log data provided. Other LogNodes can
+ * be chained to the end of the LogNode as desired.
+ *
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ */
+ public static void println(int priority, String tag, String msg) {
+ println(priority, tag, msg, null);
+ }
+
+ /**
+ * Prints a message at VERBOSE priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void v(String tag, String msg, Throwable tr) {
+ println(VERBOSE, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at VERBOSE priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void v(String tag, String msg) {
+ v(tag, msg, null);
+ }
+
+
+ /**
+ * Prints a message at DEBUG priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void d(String tag, String msg, Throwable tr) {
+ println(DEBUG, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at DEBUG priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void d(String tag, String msg) {
+ d(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at INFO priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void i(String tag, String msg, Throwable tr) {
+ println(INFO, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at INFO priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void i(String tag, String msg) {
+ i(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void w(String tag, String msg, Throwable tr) {
+ println(WARN, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void w(String tag, String msg) {
+ w(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void w(String tag, Throwable tr) {
+ w(tag, null, tr);
+ }
+
+ /**
+ * Prints a message at ERROR priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void e(String tag, String msg, Throwable tr) {
+ println(ERROR, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at ERROR priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void e(String tag, String msg) {
+ e(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void wtf(String tag, String msg, Throwable tr) {
+ println(ASSERT, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void wtf(String tag, String msg) {
+ wtf(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void wtf(String tag, Throwable tr) {
+ wtf(tag, null, tr);
+ }
+}
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/LogFragment.java b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/LogFragment.java
new file mode 100644
index 0000000..b302acd
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/LogFragment.java
@@ -0,0 +1,109 @@
+/*
+* Copyright 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.
+*/
+/*
+ * Copyright 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 com.example.android.common.logger;
+
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ScrollView;
+
+/**
+ * Simple fraggment which contains a LogView and uses is to output log data it receives
+ * through the LogNode interface.
+ */
+public class LogFragment extends Fragment {
+
+ private LogView mLogView;
+ private ScrollView mScrollView;
+
+ public LogFragment() {}
+
+ public View inflateViews() {
+ mScrollView = new ScrollView(getActivity());
+ ViewGroup.LayoutParams scrollParams = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ mScrollView.setLayoutParams(scrollParams);
+
+ mLogView = new LogView(getActivity());
+ ViewGroup.LayoutParams logParams = new ViewGroup.LayoutParams(scrollParams);
+ logParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ mLogView.setLayoutParams(logParams);
+ mLogView.setClickable(true);
+ mLogView.setFocusable(true);
+ mLogView.setTypeface(Typeface.MONOSPACE);
+
+ // Want to set padding as 16 dips, setPadding takes pixels. Hooray math!
+ int paddingDips = 16;
+ double scale = getResources().getDisplayMetrics().density;
+ int paddingPixels = (int) ((paddingDips * (scale)) + .5);
+ mLogView.setPadding(paddingPixels, paddingPixels, paddingPixels, paddingPixels);
+ mLogView.setCompoundDrawablePadding(paddingPixels);
+
+ mLogView.setGravity(Gravity.BOTTOM);
+ mLogView.setTextAppearance(getActivity(), android.R.style.TextAppearance_Holo_Medium);
+
+ mScrollView.addView(mLogView);
+ return mScrollView;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ View result = inflateViews();
+
+ mLogView.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
+ }
+ });
+ return result;
+ }
+
+ public LogView getLogView() {
+ return mLogView;
+ }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/LogNode.java b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/LogNode.java
new file mode 100644
index 0000000..bc37cab
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/LogNode.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.common.logger;
+
+/**
+ * Basic interface for a logging system that can output to one or more targets.
+ * Note that in addition to classes that will output these logs in some format,
+ * one can also implement this interface over a filter and insert that in the chain,
+ * such that no targets further down see certain data, or see manipulated forms of the data.
+ * You could, for instance, write a "ToHtmlLoggerNode" that just converted all the log data
+ * it received to HTML and sent it along to the next node in the chain, without printing it
+ * anywhere.
+ */
+public interface LogNode {
+
+ /**
+ * Instructs first LogNode in the list to print the log data provided.
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public void println(int priority, String tag, String msg, Throwable tr);
+
+}
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/LogView.java b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/LogView.java
new file mode 100644
index 0000000..c01542b
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/LogView.java
@@ -0,0 +1,145 @@
+/*
+ * 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 com.example.android.common.logger;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.*;
+import android.widget.TextView;
+
+/** Simple TextView which is used to output log data received through the LogNode interface.
+*/
+public class LogView extends TextView implements LogNode {
+
+ public LogView(Context context) {
+ super(context);
+ }
+
+ public LogView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public LogView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * Formats the log data and prints it out to the LogView.
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ @Override
+ public void println(int priority, String tag, String msg, Throwable tr) {
+
+
+ String priorityStr = null;
+
+ // For the purposes of this View, we want to print the priority as readable text.
+ switch(priority) {
+ case android.util.Log.VERBOSE:
+ priorityStr = "VERBOSE";
+ break;
+ case android.util.Log.DEBUG:
+ priorityStr = "DEBUG";
+ break;
+ case android.util.Log.INFO:
+ priorityStr = "INFO";
+ break;
+ case android.util.Log.WARN:
+ priorityStr = "WARN";
+ break;
+ case android.util.Log.ERROR:
+ priorityStr = "ERROR";
+ break;
+ case android.util.Log.ASSERT:
+ priorityStr = "ASSERT";
+ break;
+ default:
+ break;
+ }
+
+ // Handily, the Log class has a facility for converting a stack trace into a usable string.
+ String exceptionStr = null;
+ if (tr != null) {
+ exceptionStr = android.util.Log.getStackTraceString(tr);
+ }
+
+ // Take the priority, tag, message, and exception, and concatenate as necessary
+ // into one usable line of text.
+ final StringBuilder outputBuilder = new StringBuilder();
+
+ String delimiter = "\t";
+ appendIfNotNull(outputBuilder, priorityStr, delimiter);
+ appendIfNotNull(outputBuilder, tag, delimiter);
+ appendIfNotNull(outputBuilder, msg, delimiter);
+ appendIfNotNull(outputBuilder, exceptionStr, delimiter);
+
+ // In case this was originally called from an AsyncTask or some other off-UI thread,
+ // make sure the update occurs within the UI thread.
+ ((Activity) getContext()).runOnUiThread( (new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // Display the text we just generated within the LogView.
+ appendToLog(outputBuilder.toString());
+ }
+ })));
+
+ if (mNext != null) {
+ mNext.println(priority, tag, msg, tr);
+ }
+ }
+
+ public LogNode getNext() {
+ return mNext;
+ }
+
+ public void setNext(LogNode node) {
+ mNext = node;
+ }
+
+ /** Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since
+ * the logger takes so many arguments that might be null, this method helps cut out some of the
+ * agonizing tedium of writing the same 3 lines over and over.
+ * @param source StringBuilder containing the text to append to.
+ * @param addStr The String to append
+ * @param delimiter The String to separate the source and appended strings. A tab or comma,
+ * for instance.
+ * @return The fully concatenated String as a StringBuilder
+ */
+ private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) {
+ if (addStr != null) {
+ if (addStr.length() == 0) {
+ delimiter = "";
+ }
+
+ return source.append(addStr).append(delimiter);
+ }
+ return source;
+ }
+
+ // The next LogNode in the chain.
+ LogNode mNext;
+
+ /** Outputs the string as a new line of log data in the LogView. */
+ public void appendToLog(String s) {
+ append("\n" + s);
+ }
+
+
+}
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/LogWrapper.java b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/LogWrapper.java
new file mode 100644
index 0000000..16a9e7b
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/LogWrapper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.common.logger;
+
+import android.util.Log;
+
+/**
+ * Helper class which wraps Android's native Log utility in the Logger interface. This way
+ * normal DDMS output can be one of the many targets receiving and outputting logs simultaneously.
+ */
+public class LogWrapper implements LogNode {
+
+ // For piping: The next node to receive Log data after this one has done its work.
+ private LogNode mNext;
+
+ /**
+ * Returns the next LogNode in the linked list.
+ */
+ public LogNode getNext() {
+ return mNext;
+ }
+
+ /**
+ * Sets the LogNode data will be sent to..
+ */
+ public void setNext(LogNode node) {
+ mNext = node;
+ }
+
+ /**
+ * Prints data out to the console using Android's native log mechanism.
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ @Override
+ public void println(int priority, String tag, String msg, Throwable tr) {
+ // There actually are log methods that don't take a msg parameter. For now,
+ // if that's the case, just convert null to the empty string and move on.
+ String useMsg = msg;
+ if (useMsg == null) {
+ useMsg = "";
+ }
+
+ // If an exeption was provided, convert that exception to a usable string and attach
+ // it to the end of the msg method.
+ if (tr != null) {
+ msg += "\n" + Log.getStackTraceString(tr);
+ }
+
+ // This is functionally identical to Log.x(tag, useMsg);
+ // For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg)
+ Log.println(priority, tag, useMsg);
+
+ // If this isn't the last node in the chain, move things along.
+ if (mNext != null) {
+ mNext.println(priority, tag, msg, tr);
+ }
+ }
+}
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java
new file mode 100644
index 0000000..19967dc
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java
@@ -0,0 +1,60 @@
+/*
+ * 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 com.example.android.common.logger;
+
+/**
+ * Simple {@link LogNode} filter, removes everything except the message.
+ * Useful for situations like on-screen log output where you don't want a lot of metadata displayed,
+ * just easy-to-read message updates as they're happening.
+ */
+public class MessageOnlyLogFilter implements LogNode {
+
+ LogNode mNext;
+
+ /**
+ * Takes the "next" LogNode as a parameter, to simplify chaining.
+ *
+ * @param next The next LogNode in the pipeline.
+ */
+ public MessageOnlyLogFilter(LogNode next) {
+ mNext = next;
+ }
+
+ public MessageOnlyLogFilter() {
+ }
+
+ @Override
+ public void println(int priority, String tag, String msg, Throwable tr) {
+ if (mNext != null) {
+ getNext().println(Log.NONE, null, msg, null);
+ }
+ }
+
+ /**
+ * Returns the next LogNode in the chain.
+ */
+ public LogNode getNext() {
+ return mNext;
+ }
+
+ /**
+ * Sets the LogNode data will be sent to..
+ */
+ public void setNext(LogNode node) {
+ mNext = node;
+ }
+
+}
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/view/SlidingTabLayout.java b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/view/SlidingTabLayout.java
new file mode 100644
index 0000000..20049e3
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/view/SlidingTabLayout.java
@@ -0,0 +1,314 @@
+/*
+ * 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 com.example.android.common.view;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.HorizontalScrollView;
+import android.widget.TextView;
+
+/**
+ * To be used with ViewPager to provide a tab indicator component which give constant feedback as to
+ * the user's scroll progress.
+ * <p>
+ * To use the component, simply add it to your view hierarchy. Then in your
+ * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call
+ * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for.
+ * <p>
+ * The colors can be customized in two ways. The first and simplest is to provide an array of colors
+ * via {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)}. The
+ * alternative is via the {@link TabColorizer} interface which provides you complete control over
+ * which color is used for any individual position.
+ * <p>
+ * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)},
+ * providing the layout ID of your custom layout.
+ */
+public class SlidingTabLayout extends HorizontalScrollView {
+
+ /**
+ * Allows complete control over the colors drawn in the tab layout. Set with
+ * {@link #setCustomTabColorizer(TabColorizer)}.
+ */
+ public interface TabColorizer {
+
+ /**
+ * @return return the color of the indicator used when {@code position} is selected.
+ */
+ int getIndicatorColor(int position);
+
+ /**
+ * @return return the color of the divider drawn to the right of {@code position}.
+ */
+ int getDividerColor(int position);
+
+ }
+
+ private static final int TITLE_OFFSET_DIPS = 24;
+ private static final int TAB_VIEW_PADDING_DIPS = 16;
+ private static final int TAB_VIEW_TEXT_SIZE_SP = 12;
+
+ private int mTitleOffset;
+
+ private int mTabViewLayoutId;
+ private int mTabViewTextViewId;
+
+ private ViewPager mViewPager;
+ private ViewPager.OnPageChangeListener mViewPagerPageChangeListener;
+
+ private final SlidingTabStrip mTabStrip;
+
+ public SlidingTabLayout(Context context) {
+ this(context, null);
+ }
+
+ public SlidingTabLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ // Disable the Scroll Bar
+ setHorizontalScrollBarEnabled(false);
+ // Make sure that the Tab Strips fills this View
+ setFillViewport(true);
+
+ mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
+
+ mTabStrip = new SlidingTabStrip(context);
+ addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ /**
+ * Set the custom {@link TabColorizer} to be used.
+ *
+ * If you only require simple custmisation then you can use
+ * {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)} to achieve
+ * similar effects.
+ */
+ public void setCustomTabColorizer(TabColorizer tabColorizer) {
+ mTabStrip.setCustomTabColorizer(tabColorizer);
+ }
+
+ /**
+ * Sets the colors to be used for indicating the selected tab. These colors are treated as a
+ * circular array. Providing one color will mean that all tabs are indicated with the same color.
+ */
+ public void setSelectedIndicatorColors(int... colors) {
+ mTabStrip.setSelectedIndicatorColors(colors);
+ }
+
+ /**
+ * Sets the colors to be used for tab dividers. These colors are treated as a circular array.
+ * Providing one color will mean that all tabs are indicated with the same color.
+ */
+ public void setDividerColors(int... colors) {
+ mTabStrip.setDividerColors(colors);
+ }
+
+ /**
+ * Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are
+ * required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so
+ * that the layout can update it's scroll position correctly.
+ *
+ * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener)
+ */
+ public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
+ mViewPagerPageChangeListener = listener;
+ }
+
+ /**
+ * Set the custom layout to be inflated for the tab views.
+ *
+ * @param layoutResId Layout id to be inflated
+ * @param textViewId id of the {@link TextView} in the inflated view
+ */
+ public void setCustomTabView(int layoutResId, int textViewId) {
+ mTabViewLayoutId = layoutResId;
+ mTabViewTextViewId = textViewId;
+ }
+
+ /**
+ * Sets the associated view pager. Note that the assumption here is that the pager content
+ * (number of tabs and tab titles) does not change after this call has been made.
+ */
+ public void setViewPager(ViewPager viewPager) {
+ mTabStrip.removeAllViews();
+
+ mViewPager = viewPager;
+ if (viewPager != null) {
+ viewPager.setOnPageChangeListener(new InternalViewPagerListener());
+ populateTabStrip();
+ }
+ }
+
+ /**
+ * Create a default view to be used for tabs. This is called if a custom tab view is not set via
+ * {@link #setCustomTabView(int, int)}.
+ */
+ protected TextView createDefaultTabView(Context context) {
+ TextView textView = new TextView(context);
+ textView.setGravity(Gravity.CENTER);
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP);
+ textView.setTypeface(Typeface.DEFAULT_BOLD);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ // If we're running on Honeycomb or newer, then we can use the Theme's
+ // selectableItemBackground to ensure that the View has a pressed state
+ TypedValue outValue = new TypedValue();
+ getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground,
+ outValue, true);
+ textView.setBackgroundResource(outValue.resourceId);
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ // If we're running on ICS or newer, enable all-caps to match the Action Bar tab style
+ textView.setAllCaps(true);
+ }
+
+ int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density);
+ textView.setPadding(padding, padding, padding, padding);
+
+ return textView;
+ }
+
+ private void populateTabStrip() {
+ final PagerAdapter adapter = mViewPager.getAdapter();
+ final View.OnClickListener tabClickListener = new TabClickListener();
+
+ for (int i = 0; i < adapter.getCount(); i++) {
+ View tabView = null;
+ TextView tabTitleView = null;
+
+ if (mTabViewLayoutId != 0) {
+ // If there is a custom tab view layout id set, try and inflate it
+ tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip,
+ false);
+ tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId);
+ }
+
+ if (tabView == null) {
+ tabView = createDefaultTabView(getContext());
+ }
+
+ if (tabTitleView == null && TextView.class.isInstance(tabView)) {
+ tabTitleView = (TextView) tabView;
+ }
+
+ tabTitleView.setText(adapter.getPageTitle(i));
+ tabView.setOnClickListener(tabClickListener);
+
+ mTabStrip.addView(tabView);
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (mViewPager != null) {
+ scrollToTab(mViewPager.getCurrentItem(), 0);
+ }
+ }
+
+ private void scrollToTab(int tabIndex, int positionOffset) {
+ final int tabStripChildCount = mTabStrip.getChildCount();
+ if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) {
+ return;
+ }
+
+ View selectedChild = mTabStrip.getChildAt(tabIndex);
+ if (selectedChild != null) {
+ int targetScrollX = selectedChild.getLeft() + positionOffset;
+
+ if (tabIndex > 0 || positionOffset > 0) {
+ // If we're not at the first child and are mid-scroll, make sure we obey the offset
+ targetScrollX -= mTitleOffset;
+ }
+
+ scrollTo(targetScrollX, 0);
+ }
+ }
+
+ private class InternalViewPagerListener implements ViewPager.OnPageChangeListener {
+ private int mScrollState;
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ int tabStripChildCount = mTabStrip.getChildCount();
+ if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
+ return;
+ }
+
+ mTabStrip.onViewPagerPageChanged(position, positionOffset);
+
+ View selectedTitle = mTabStrip.getChildAt(position);
+ int extraOffset = (selectedTitle != null)
+ ? (int) (positionOffset * selectedTitle.getWidth())
+ : 0;
+ scrollToTab(position, extraOffset);
+
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageScrolled(position, positionOffset,
+ positionOffsetPixels);
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ mScrollState = state;
+
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageScrollStateChanged(state);
+ }
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
+ mTabStrip.onViewPagerPageChanged(position, 0f);
+ scrollToTab(position, 0);
+ }
+
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageSelected(position);
+ }
+ }
+
+ }
+
+ private class TabClickListener implements View.OnClickListener {
+ @Override
+ public void onClick(View v) {
+ for (int i = 0; i < mTabStrip.getChildCount(); i++) {
+ if (v == mTabStrip.getChildAt(i)) {
+ mViewPager.setCurrentItem(i);
+ return;
+ }
+ }
+ }
+ }
+
+}
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/view/SlidingTabStrip.java b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/view/SlidingTabStrip.java
new file mode 100644
index 0000000..d5bbbae
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/common/view/SlidingTabStrip.java
@@ -0,0 +1,208 @@
+/*
+ * 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 com.example.android.common.view;
+
+import android.R;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.LinearLayout;
+
+class SlidingTabStrip extends LinearLayout {
+
+ private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 2;
+ private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26;
+ private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 8;
+ private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5;
+
+ private static final int DEFAULT_DIVIDER_THICKNESS_DIPS = 1;
+ private static final byte DEFAULT_DIVIDER_COLOR_ALPHA = 0x20;
+ private static final float DEFAULT_DIVIDER_HEIGHT = 0.5f;
+
+ private final int mBottomBorderThickness;
+ private final Paint mBottomBorderPaint;
+
+ private final int mSelectedIndicatorThickness;
+ private final Paint mSelectedIndicatorPaint;
+
+ private final int mDefaultBottomBorderColor;
+
+ private final Paint mDividerPaint;
+ private final float mDividerHeight;
+
+ private int mSelectedPosition;
+ private float mSelectionOffset;
+
+ private SlidingTabLayout.TabColorizer mCustomTabColorizer;
+ private final SimpleTabColorizer mDefaultTabColorizer;
+
+ SlidingTabStrip(Context context) {
+ this(context, null);
+ }
+
+ SlidingTabStrip(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setWillNotDraw(false);
+
+ final float density = getResources().getDisplayMetrics().density;
+
+ TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.colorForeground, outValue, true);
+ final int themeForegroundColor = outValue.data;
+
+ mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor,
+ DEFAULT_BOTTOM_BORDER_COLOR_ALPHA);
+
+ mDefaultTabColorizer = new SimpleTabColorizer();
+ mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR);
+ mDefaultTabColorizer.setDividerColors(setColorAlpha(themeForegroundColor,
+ DEFAULT_DIVIDER_COLOR_ALPHA));
+
+ mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density);
+ mBottomBorderPaint = new Paint();
+ mBottomBorderPaint.setColor(mDefaultBottomBorderColor);
+
+ mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density);
+ mSelectedIndicatorPaint = new Paint();
+
+ mDividerHeight = DEFAULT_DIVIDER_HEIGHT;
+ mDividerPaint = new Paint();
+ mDividerPaint.setStrokeWidth((int) (DEFAULT_DIVIDER_THICKNESS_DIPS * density));
+ }
+
+ void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) {
+ mCustomTabColorizer = customTabColorizer;
+ invalidate();
+ }
+
+ void setSelectedIndicatorColors(int... colors) {
+ // Make sure that the custom colorizer is removed
+ mCustomTabColorizer = null;
+ mDefaultTabColorizer.setIndicatorColors(colors);
+ invalidate();
+ }
+
+ void setDividerColors(int... colors) {
+ // Make sure that the custom colorizer is removed
+ mCustomTabColorizer = null;
+ mDefaultTabColorizer.setDividerColors(colors);
+ invalidate();
+ }
+
+ void onViewPagerPageChanged(int position, float positionOffset) {
+ mSelectedPosition = position;
+ mSelectionOffset = positionOffset;
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ final int height = getHeight();
+ final int childCount = getChildCount();
+ final int dividerHeightPx = (int) (Math.min(Math.max(0f, mDividerHeight), 1f) * height);
+ final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null
+ ? mCustomTabColorizer
+ : mDefaultTabColorizer;
+
+ // Thick colored underline below the current selection
+ if (childCount > 0) {
+ View selectedTitle = getChildAt(mSelectedPosition);
+ int left = selectedTitle.getLeft();
+ int right = selectedTitle.getRight();
+ int color = tabColorizer.getIndicatorColor(mSelectedPosition);
+
+ if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
+ int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1);
+ if (color != nextColor) {
+ color = blendColors(nextColor, color, mSelectionOffset);
+ }
+
+ // Draw the selection partway between the tabs
+ View nextTitle = getChildAt(mSelectedPosition + 1);
+ left = (int) (mSelectionOffset * nextTitle.getLeft() +
+ (1.0f - mSelectionOffset) * left);
+ right = (int) (mSelectionOffset * nextTitle.getRight() +
+ (1.0f - mSelectionOffset) * right);
+ }
+
+ mSelectedIndicatorPaint.setColor(color);
+
+ canvas.drawRect(left, height - mSelectedIndicatorThickness, right,
+ height, mSelectedIndicatorPaint);
+ }
+
+ // Thin underline along the entire bottom edge
+ canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint);
+
+ // Vertical separators between the titles
+ int separatorTop = (height - dividerHeightPx) / 2;
+ for (int i = 0; i < childCount - 1; i++) {
+ View child = getChildAt(i);
+ mDividerPaint.setColor(tabColorizer.getDividerColor(i));
+ canvas.drawLine(child.getRight(), separatorTop, child.getRight(),
+ separatorTop + dividerHeightPx, mDividerPaint);
+ }
+ }
+
+ /**
+ * Set the alpha value of the {@code color} to be the given {@code alpha} value.
+ */
+ private static int setColorAlpha(int color, byte alpha) {
+ return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ /**
+ * Blend {@code color1} and {@code color2} using the given ratio.
+ *
+ * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend,
+ * 0.0 will return {@code color2}.
+ */
+ private static int blendColors(int color1, int color2, float ratio) {
+ final float inverseRation = 1f - ratio;
+ float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation);
+ float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation);
+ float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation);
+ return Color.rgb((int) r, (int) g, (int) b);
+ }
+
+ private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer {
+ private int[] mIndicatorColors;
+ private int[] mDividerColors;
+
+ @Override
+ public final int getIndicatorColor(int position) {
+ return mIndicatorColors[position % mIndicatorColors.length];
+ }
+
+ @Override
+ public final int getDividerColor(int position) {
+ return mDividerColors[position % mDividerColors.length];
+ }
+
+ void setIndicatorColors(int... colors) {
+ mIndicatorColors = colors;
+ }
+
+ void setDividerColors(int... colors) {
+ mDividerColors = colors;
+ }
+ }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-hdpi/ic_launcher.png b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..0f5d360
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-hdpi/tile.9.png b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-hdpi/tile.9.png
new file mode 100644
index 0000000..1358628
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-hdpi/tile.9.png
Binary files differ
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-mdpi/ic_launcher.png b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..72d85c9
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-xhdpi/ic_launcher.png b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..cf93e69
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-xxhdpi/ic_launcher.png b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..149a984
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable/oval.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable/oval.xml
new file mode 100644
index 0000000..07f3abd
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/drawable/oval.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2014 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <solid android:color="#0000ff"/>
+</shape>
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout-w720dp/activity_main.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout-w720dp/activity_main.xml
new file mode 100755
index 0000000..c9a52f6
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout-w720dp/activity_main.xml
@@ -0,0 +1,73 @@
+<!--
+ Copyright 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/sample_main_layout">
+
+ <LinearLayout
+ android:id="@+id/sample_output"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <FrameLayout
+ style="@style/Widget.SampleMessageTile"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ style="@style/Widget.SampleMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/margin_medium"
+ android:paddingRight="@dimen/margin_medium"
+ android:paddingTop="@dimen/margin_large"
+ android:paddingBottom="@dimen/margin_large"
+ android:text="@string/intro_message" />
+ </FrameLayout>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@android:color/darker_gray" />
+
+ <fragment
+ android:name="com.example.android.common.logger.LogFragment"
+ android:id="@+id/log_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1" />
+
+ </LinearLayout>
+
+ <View
+ android:layout_width="1dp"
+ android:layout_height="match_parent"
+ android:background="@android:color/darker_gray" />
+
+ <FrameLayout
+ android:id="@+id/sample_content_fragment"
+ android:layout_weight="2"
+ android:layout_width="0px"
+ android:layout_height="match_parent" />
+
+</LinearLayout>
+
+
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/activity_basic_transition.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/activity_basic_transition.xml
new file mode 100644
index 0000000..f9a4cd2
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/activity_basic_transition.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2014 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="com.example.android.basictransition.BasicTransitionActivity"/>
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/activity_main.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/activity_main.xml
new file mode 100755
index 0000000..1ae4f98
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,65 @@
+<!--
+ Copyright 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/sample_main_layout">
+
+ <ViewAnimator
+ android:id="@+id/sample_output"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1">
+
+ <ScrollView
+ style="@style/Widget.SampleMessageTile"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ style="@style/Widget.SampleMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/horizontal_page_margin"
+ android:paddingRight="@dimen/horizontal_page_margin"
+ android:paddingTop="@dimen/vertical_page_margin"
+ android:paddingBottom="@dimen/vertical_page_margin"
+ android:text="@string/intro_message" />
+ </ScrollView>
+
+ <fragment
+ android:name="com.example.android.common.logger.LogFragment"
+ android:id="@+id/log_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </ViewAnimator>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@android:color/darker_gray" />
+
+ <FrameLayout
+ android:id="@+id/sample_content_fragment"
+ android:layout_weight="2"
+ android:layout_width="match_parent"
+ android:layout_height="0px" />
+
+</LinearLayout>
+
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/fragment_basic_transition.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/fragment_basic_transition.xml
new file mode 100644
index 0000000..98999c8
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/fragment_basic_transition.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2014 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context="com.example.android.basictransition.BasicTransitionFragment">
+
+ <RadioGroup
+ android:id="@+id/select_scene"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/scene"/>
+
+ <RadioButton
+ android:id="@+id/select_scene_1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:checked="true"
+ android:text="@string/scene_1"/>
+
+ <RadioButton
+ android:id="@+id/select_scene_2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/scene_2"/>
+
+ <RadioButton
+ android:id="@+id/select_scene_3"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/scene_3"/>
+
+ <RadioButton
+ android:id="@+id/select_scene_4"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/scene_4"/>
+
+ </RadioGroup>
+
+ <FrameLayout
+ android:id="@+id/scene_root"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+
+ <include layout="@layout/scene1"/>
+
+ </FrameLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/scene1.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/scene1.xml
new file mode 100644
index 0000000..005bf3b
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/scene1.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2014 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.
+-->
+<RelativeLayout
+ android:id="@+id/container"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <View
+ android:id="@+id/transition_square"
+ android:layout_width="@dimen/square_size_normal"
+ android:layout_height="@dimen/square_size_normal"
+ android:background="#990000"
+ android:gravity="center"/>
+
+ <ImageView
+ android:id="@+id/transition_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/transition_square"
+ android:src="@drawable/ic_launcher"/>
+
+ <ImageView
+ android:id="@+id/transition_oval"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_below="@id/transition_image"
+ android:src="@drawable/oval"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/scene2.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/scene2.xml
new file mode 100644
index 0000000..38a809a
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/scene2.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2014 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.
+-->
+<RelativeLayout
+ android:id="@+id/container"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <View
+ android:id="@+id/transition_square"
+ android:layout_width="@dimen/square_size_normal"
+ android:layout_height="@dimen/square_size_normal"
+ android:layout_alignParentBottom="true"
+ android:background="#990000"
+ android:gravity="center"/>
+
+ <ImageView
+ android:id="@+id/transition_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentRight="true"
+ android:src="@drawable/ic_launcher"/>
+
+ <ImageView
+ android:id="@+id/transition_oval"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_centerHorizontal="true"
+ android:src="@drawable/oval"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/scene3.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/scene3.xml
new file mode 100644
index 0000000..06246da
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/layout/scene3.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2014 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.
+-->
+<RelativeLayout
+ android:id="@+id/container"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <View
+ android:id="@+id/transition_square"
+ android:layout_width="@dimen/square_size_normal"
+ android:layout_height="@dimen/square_size_normal"
+ android:layout_centerHorizontal="true"
+ android:background="#990000"
+ android:gravity="center"/>
+
+ <ImageView
+ android:id="@+id/transition_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:src="@drawable/ic_launcher"/>
+
+ <ImageView
+ android:id="@+id/transition_oval"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentRight="true"
+ android:src="@drawable/oval"/>
+
+ <TextView
+ android:id="@+id/transition_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:text="@string/this_is_scene_3"
+ android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/menu/main.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/menu/main.xml
new file mode 100644
index 0000000..b49c2c5
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/menu/main.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright 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.
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/menu_toggle_log"
+ android:showAsAction="always"
+ android:title="@string/sample_show_log" />
+</menu>
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/transition/changebounds_fadein_together.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/transition/changebounds_fadein_together.xml
new file mode 100644
index 0000000..062e012
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/transition/changebounds_fadein_together.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2014 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.
+-->
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
+ <changeBounds/>
+ <fade android:fadingMode="fade_in">
+ <targets>
+ <target android:targetId="@id/transition_title" />
+ </targets>
+ </fade>
+</transitionSet>
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/transition/scene3_transition_manager.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/transition/scene3_transition_manager.xml
new file mode 100644
index 0000000..6189d61
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/transition/scene3_transition_manager.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2014 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.
+-->
+<transitionManager xmlns:android="http://schemas.android.com/apk/res/android">
+ <transition
+ android:toScene="@layout/scene3"
+ android:transition="@transition/changebounds_fadein_together"/>
+</transitionManager>
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values-sw600dp/template-dimens.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values-sw600dp/template-dimens.xml
new file mode 100644
index 0000000..22074a2
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values-sw600dp/template-dimens.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <!-- Semantic definitions -->
+
+ <dimen name="horizontal_page_margin">@dimen/margin_huge</dimen>
+ <dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
+
+</resources>
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values-sw600dp/template-styles.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values-sw600dp/template-styles.xml
new file mode 100644
index 0000000..03d1974
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values-sw600dp/template-styles.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <style name="Widget.SampleMessage">
+ <item name="android:textAppearance">?android:textAppearanceLarge</item>
+ <item name="android:lineSpacingMultiplier">1.2</item>
+ <item name="android:shadowDy">-6.5</item>
+ </style>
+
+</resources>
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values-w820dp/dimens.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..21e2968
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2014 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.
+-->
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/base-strings.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/base-strings.xml
new file mode 100644
index 0000000..466e590
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/base-strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 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.
+-->
+
+
+
+<resources>
+ <string name="app_name">BasicTransition</string>
+ <string name="intro_message">
+ <![CDATA[
+
+
+ This sample demonstrates the basic use of the transition framework introduced in KitKat.
+ Select each of the RadioButtons to switch between the Scenes.
+
+
+ ]]>
+ </string>
+</resources>
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/dimens.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..45ccdbc
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2014 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.
+-->
+<resources>
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+ <dimen name="square_size_normal">50dp</dimen>
+ <dimen name="square_size_expanded">100dp</dimen>
+</resources>
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/strings.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/strings.xml
new file mode 100755
index 0000000..7b9d9ec
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+ Copyright 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.
+-->
+<resources>
+ <string name="sample_show_log">Show Log</string>
+ <string name="sample_hide_log">Hide Log</string>
+</resources>
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/template-dimens.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/template-dimens.xml
new file mode 100644
index 0000000..39e710b
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/template-dimens.xml
@@ -0,0 +1,32 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <!-- Define standard dimensions to comply with Holo-style grids and rhythm. -->
+
+ <dimen name="margin_tiny">4dp</dimen>
+ <dimen name="margin_small">8dp</dimen>
+ <dimen name="margin_medium">16dp</dimen>
+ <dimen name="margin_large">32dp</dimen>
+ <dimen name="margin_huge">64dp</dimen>
+
+ <!-- Semantic definitions -->
+
+ <dimen name="horizontal_page_margin">@dimen/margin_medium</dimen>
+ <dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
+
+</resources>
diff --git a/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/template-styles.xml b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/template-styles.xml
new file mode 100644
index 0000000..404623e
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/BasicTransitionSample/src/main/res/values/template-styles.xml
@@ -0,0 +1,42 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <!-- Activity themes -->
+
+ <style name="Theme.Base" parent="android:Theme.Holo.Light" />
+
+ <style name="Theme.Sample" parent="Theme.Base" />
+
+ <style name="AppTheme" parent="Theme.Sample" />
+ <!-- Widget styling -->
+
+ <style name="Widget" />
+
+ <style name="Widget.SampleMessage">
+ <item name="android:textAppearance">?android:textAppearanceMedium</item>
+ <item name="android:lineSpacingMultiplier">1.1</item>
+ </style>
+
+ <style name="Widget.SampleMessageTile">
+ <item name="android:background">@drawable/tile</item>
+ <item name="android:shadowColor">#7F000000</item>
+ <item name="android:shadowDy">-3.5</item>
+ <item name="android:shadowRadius">2</item>
+ </style>
+
+</resources>
diff --git a/prebuilts/gradle/BasicTransition/README.txt b/prebuilts/gradle/BasicTransition/README.txt
new file mode 100644
index 0000000..9616c58
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/README.txt
@@ -0,0 +1,18 @@
+Build Instructions
+-------------------
+
+This sample uses the Gradle build system. To build this project, use the
+"gradlew build" command or use "Import Project" in Android Studio.
+
+To see a list of all available commands, run "gradlew tasks".
+
+Dependencies
+-------------
+
+- Android SDK Build-tools v18.1
+- Android Support Repository v2
+
+Dependencies are available for download via the Android SDK Manager.
+
+Android Studio is available for download at:
+ http://developer.android.com/sdk/installing/studio.html
diff --git a/prebuilts/gradle/BasicTransition/build.gradle b/prebuilts/gradle/BasicTransition/build.gradle
new file mode 100644
index 0000000..584ba87
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/build.gradle
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/prebuilts/gradle/BasicTransition/gradle/wrapper/gradle-wrapper.jar b/prebuilts/gradle/BasicTransition/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/prebuilts/gradle/BasicTransition/gradle/wrapper/gradle-wrapper.properties b/prebuilts/gradle/BasicTransition/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..56f685a
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip
diff --git a/prebuilts/gradle/BasicTransition/gradlew b/prebuilts/gradle/BasicTransition/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/prebuilts/gradle/BasicTransition/gradlew.bat b/prebuilts/gradle/BasicTransition/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/prebuilts/gradle/BasicTransition/settings.gradle b/prebuilts/gradle/BasicTransition/settings.gradle
new file mode 100644
index 0000000..fae2688
--- /dev/null
+++ b/prebuilts/gradle/BasicTransition/settings.gradle
@@ -0,0 +1 @@
+include 'BasicTransitionSample'
diff --git a/prebuilts/gradle/DisplayingBitmaps/ABOUT.txt b/prebuilts/gradle/DisplayingBitmaps/ABOUT.txt
new file mode 100644
index 0000000..2cf2fa5
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/ABOUT.txt
@@ -0,0 +1,22 @@
+Copyright (C) 2014 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.
+
+---------------------------------------
+
+This is a sample application for the Android Training class "Displaying Bitmaps
+Efficiently" (http://developer.android.com/training/displaying-bitmaps/).
+
+It demonstrates how to load large bitmaps efficiently off the main UI thread,
+caching bitmaps (both in memory and on disk), managing bitmap memory and
+displaying bitmaps in UI elements such as ViewPager and ListView/GridView.
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/build.gradle b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/build.gradle
new file mode 100644
index 0000000..75a1785
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/build.gradle
@@ -0,0 +1,63 @@
+
+
+
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.8.+'
+ }
+}
+
+apply plugin: 'android'
+
+dependencies {
+
+ // Add the support lib that is appropriate for SDK 7
+ compile "com.android.support:support-v4:19.0.+"
+ compile "com.android.support:gridlayout-v7:19.0.+"
+
+
+}
+
+// The sample build uses multiple directories to
+// keep boilerplate and common code separate from
+// the main sample code.
+List<String> dirs = [
+ 'main', // main sample code; look here for the interesting stuff.
+ 'common', // components that are reused by multiple samples
+ 'template'] // boilerplate code that is generated by the sample template process
+
+android {
+ compileSdkVersion 19
+
+ buildToolsVersion "19.0.1"
+
+ sourceSets {
+ main {
+ dirs.each { dir ->
+ java.srcDirs "src/${dir}/java"
+ res.srcDirs "src/${dir}/res"
+ }
+ }
+ instrumentTest.setRoot('tests')
+ instrumentTest.java.srcDirs = ['tests/src']
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/AndroidManifest.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..23db308
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/AndroidManifest.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.displayingbitmaps"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
+ <application android:allowBackup="true"
+ android:label="@string/app_name"
+ android:description="@string/intro_message"
+ android:icon="@drawable/ic_launcher"
+ android:theme="@style/AppThemeDark">
+
+ <activity android:name=".ui.ImageGridActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name=".ui.ImageDetailActivity"
+ android:label="@string/app_name"
+ android:parentActivityName=".ui.ImageGridActivity"
+ android:theme="@style/AppThemeDark.FullScreen" >
+ <meta-data android:name="android.support.PARENT_ACTIVITY"
+ android:value=".ui.ImageGridActivity" />
+ </activity>
+
+ </application>
+
+</manifest>
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/Log.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/Log.java
new file mode 100644
index 0000000..17503c5
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/Log.java
@@ -0,0 +1,236 @@
+/*
+ * 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 com.example.android.common.logger;
+
+/**
+ * Helper class for a list (or tree) of LoggerNodes.
+ *
+ * <p>When this is set as the head of the list,
+ * an instance of it can function as a drop-in replacement for {@link android.util.Log}.
+ * Most of the methods in this class server only to map a method call in Log to its equivalent
+ * in LogNode.</p>
+ */
+public class Log {
+ // Grabbing the native values from Android's native logging facilities,
+ // to make for easy migration and interop.
+ public static final int NONE = -1;
+ public static final int VERBOSE = android.util.Log.VERBOSE;
+ public static final int DEBUG = android.util.Log.DEBUG;
+ public static final int INFO = android.util.Log.INFO;
+ public static final int WARN = android.util.Log.WARN;
+ public static final int ERROR = android.util.Log.ERROR;
+ public static final int ASSERT = android.util.Log.ASSERT;
+
+ // Stores the beginning of the LogNode topology.
+ private static LogNode mLogNode;
+
+ /**
+ * Returns the next LogNode in the linked list.
+ */
+ public static LogNode getLogNode() {
+ return mLogNode;
+ }
+
+ /**
+ * Sets the LogNode data will be sent to.
+ */
+ public static void setLogNode(LogNode node) {
+ mLogNode = node;
+ }
+
+ /**
+ * Instructs the LogNode to print the log data provided. Other LogNodes can
+ * be chained to the end of the LogNode as desired.
+ *
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void println(int priority, String tag, String msg, Throwable tr) {
+ if (mLogNode != null) {
+ mLogNode.println(priority, tag, msg, tr);
+ }
+ }
+
+ /**
+ * Instructs the LogNode to print the log data provided. Other LogNodes can
+ * be chained to the end of the LogNode as desired.
+ *
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ */
+ public static void println(int priority, String tag, String msg) {
+ println(priority, tag, msg, null);
+ }
+
+ /**
+ * Prints a message at VERBOSE priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void v(String tag, String msg, Throwable tr) {
+ println(VERBOSE, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at VERBOSE priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void v(String tag, String msg) {
+ v(tag, msg, null);
+ }
+
+
+ /**
+ * Prints a message at DEBUG priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void d(String tag, String msg, Throwable tr) {
+ println(DEBUG, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at DEBUG priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void d(String tag, String msg) {
+ d(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at INFO priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void i(String tag, String msg, Throwable tr) {
+ println(INFO, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at INFO priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void i(String tag, String msg) {
+ i(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void w(String tag, String msg, Throwable tr) {
+ println(WARN, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void w(String tag, String msg) {
+ w(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void w(String tag, Throwable tr) {
+ w(tag, null, tr);
+ }
+
+ /**
+ * Prints a message at ERROR priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void e(String tag, String msg, Throwable tr) {
+ println(ERROR, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at ERROR priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void e(String tag, String msg) {
+ e(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void wtf(String tag, String msg, Throwable tr) {
+ println(ASSERT, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void wtf(String tag, String msg) {
+ wtf(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void wtf(String tag, Throwable tr) {
+ wtf(tag, null, tr);
+ }
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/LogFragment.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/LogFragment.java
new file mode 100644
index 0000000..b302acd
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/LogFragment.java
@@ -0,0 +1,109 @@
+/*
+* Copyright 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.
+*/
+/*
+ * Copyright 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 com.example.android.common.logger;
+
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ScrollView;
+
+/**
+ * Simple fraggment which contains a LogView and uses is to output log data it receives
+ * through the LogNode interface.
+ */
+public class LogFragment extends Fragment {
+
+ private LogView mLogView;
+ private ScrollView mScrollView;
+
+ public LogFragment() {}
+
+ public View inflateViews() {
+ mScrollView = new ScrollView(getActivity());
+ ViewGroup.LayoutParams scrollParams = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ mScrollView.setLayoutParams(scrollParams);
+
+ mLogView = new LogView(getActivity());
+ ViewGroup.LayoutParams logParams = new ViewGroup.LayoutParams(scrollParams);
+ logParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ mLogView.setLayoutParams(logParams);
+ mLogView.setClickable(true);
+ mLogView.setFocusable(true);
+ mLogView.setTypeface(Typeface.MONOSPACE);
+
+ // Want to set padding as 16 dips, setPadding takes pixels. Hooray math!
+ int paddingDips = 16;
+ double scale = getResources().getDisplayMetrics().density;
+ int paddingPixels = (int) ((paddingDips * (scale)) + .5);
+ mLogView.setPadding(paddingPixels, paddingPixels, paddingPixels, paddingPixels);
+ mLogView.setCompoundDrawablePadding(paddingPixels);
+
+ mLogView.setGravity(Gravity.BOTTOM);
+ mLogView.setTextAppearance(getActivity(), android.R.style.TextAppearance_Holo_Medium);
+
+ mScrollView.addView(mLogView);
+ return mScrollView;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ View result = inflateViews();
+
+ mLogView.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
+ }
+ });
+ return result;
+ }
+
+ public LogView getLogView() {
+ return mLogView;
+ }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/LogNode.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/LogNode.java
new file mode 100644
index 0000000..bc37cab
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/LogNode.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.common.logger;
+
+/**
+ * Basic interface for a logging system that can output to one or more targets.
+ * Note that in addition to classes that will output these logs in some format,
+ * one can also implement this interface over a filter and insert that in the chain,
+ * such that no targets further down see certain data, or see manipulated forms of the data.
+ * You could, for instance, write a "ToHtmlLoggerNode" that just converted all the log data
+ * it received to HTML and sent it along to the next node in the chain, without printing it
+ * anywhere.
+ */
+public interface LogNode {
+
+ /**
+ * Instructs first LogNode in the list to print the log data provided.
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public void println(int priority, String tag, String msg, Throwable tr);
+
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/LogView.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/LogView.java
new file mode 100644
index 0000000..c01542b
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/LogView.java
@@ -0,0 +1,145 @@
+/*
+ * 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 com.example.android.common.logger;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.*;
+import android.widget.TextView;
+
+/** Simple TextView which is used to output log data received through the LogNode interface.
+*/
+public class LogView extends TextView implements LogNode {
+
+ public LogView(Context context) {
+ super(context);
+ }
+
+ public LogView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public LogView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * Formats the log data and prints it out to the LogView.
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ @Override
+ public void println(int priority, String tag, String msg, Throwable tr) {
+
+
+ String priorityStr = null;
+
+ // For the purposes of this View, we want to print the priority as readable text.
+ switch(priority) {
+ case android.util.Log.VERBOSE:
+ priorityStr = "VERBOSE";
+ break;
+ case android.util.Log.DEBUG:
+ priorityStr = "DEBUG";
+ break;
+ case android.util.Log.INFO:
+ priorityStr = "INFO";
+ break;
+ case android.util.Log.WARN:
+ priorityStr = "WARN";
+ break;
+ case android.util.Log.ERROR:
+ priorityStr = "ERROR";
+ break;
+ case android.util.Log.ASSERT:
+ priorityStr = "ASSERT";
+ break;
+ default:
+ break;
+ }
+
+ // Handily, the Log class has a facility for converting a stack trace into a usable string.
+ String exceptionStr = null;
+ if (tr != null) {
+ exceptionStr = android.util.Log.getStackTraceString(tr);
+ }
+
+ // Take the priority, tag, message, and exception, and concatenate as necessary
+ // into one usable line of text.
+ final StringBuilder outputBuilder = new StringBuilder();
+
+ String delimiter = "\t";
+ appendIfNotNull(outputBuilder, priorityStr, delimiter);
+ appendIfNotNull(outputBuilder, tag, delimiter);
+ appendIfNotNull(outputBuilder, msg, delimiter);
+ appendIfNotNull(outputBuilder, exceptionStr, delimiter);
+
+ // In case this was originally called from an AsyncTask or some other off-UI thread,
+ // make sure the update occurs within the UI thread.
+ ((Activity) getContext()).runOnUiThread( (new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // Display the text we just generated within the LogView.
+ appendToLog(outputBuilder.toString());
+ }
+ })));
+
+ if (mNext != null) {
+ mNext.println(priority, tag, msg, tr);
+ }
+ }
+
+ public LogNode getNext() {
+ return mNext;
+ }
+
+ public void setNext(LogNode node) {
+ mNext = node;
+ }
+
+ /** Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since
+ * the logger takes so many arguments that might be null, this method helps cut out some of the
+ * agonizing tedium of writing the same 3 lines over and over.
+ * @param source StringBuilder containing the text to append to.
+ * @param addStr The String to append
+ * @param delimiter The String to separate the source and appended strings. A tab or comma,
+ * for instance.
+ * @return The fully concatenated String as a StringBuilder
+ */
+ private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) {
+ if (addStr != null) {
+ if (addStr.length() == 0) {
+ delimiter = "";
+ }
+
+ return source.append(addStr).append(delimiter);
+ }
+ return source;
+ }
+
+ // The next LogNode in the chain.
+ LogNode mNext;
+
+ /** Outputs the string as a new line of log data in the LogView. */
+ public void appendToLog(String s) {
+ append("\n" + s);
+ }
+
+
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/LogWrapper.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/LogWrapper.java
new file mode 100644
index 0000000..16a9e7b
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/LogWrapper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.common.logger;
+
+import android.util.Log;
+
+/**
+ * Helper class which wraps Android's native Log utility in the Logger interface. This way
+ * normal DDMS output can be one of the many targets receiving and outputting logs simultaneously.
+ */
+public class LogWrapper implements LogNode {
+
+ // For piping: The next node to receive Log data after this one has done its work.
+ private LogNode mNext;
+
+ /**
+ * Returns the next LogNode in the linked list.
+ */
+ public LogNode getNext() {
+ return mNext;
+ }
+
+ /**
+ * Sets the LogNode data will be sent to..
+ */
+ public void setNext(LogNode node) {
+ mNext = node;
+ }
+
+ /**
+ * Prints data out to the console using Android's native log mechanism.
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ @Override
+ public void println(int priority, String tag, String msg, Throwable tr) {
+ // There actually are log methods that don't take a msg parameter. For now,
+ // if that's the case, just convert null to the empty string and move on.
+ String useMsg = msg;
+ if (useMsg == null) {
+ useMsg = "";
+ }
+
+ // If an exeption was provided, convert that exception to a usable string and attach
+ // it to the end of the msg method.
+ if (tr != null) {
+ msg += "\n" + Log.getStackTraceString(tr);
+ }
+
+ // This is functionally identical to Log.x(tag, useMsg);
+ // For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg)
+ Log.println(priority, tag, useMsg);
+
+ // If this isn't the last node in the chain, move things along.
+ if (mNext != null) {
+ mNext.println(priority, tag, msg, tr);
+ }
+ }
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java
new file mode 100644
index 0000000..19967dc
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java
@@ -0,0 +1,60 @@
+/*
+ * 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 com.example.android.common.logger;
+
+/**
+ * Simple {@link LogNode} filter, removes everything except the message.
+ * Useful for situations like on-screen log output where you don't want a lot of metadata displayed,
+ * just easy-to-read message updates as they're happening.
+ */
+public class MessageOnlyLogFilter implements LogNode {
+
+ LogNode mNext;
+
+ /**
+ * Takes the "next" LogNode as a parameter, to simplify chaining.
+ *
+ * @param next The next LogNode in the pipeline.
+ */
+ public MessageOnlyLogFilter(LogNode next) {
+ mNext = next;
+ }
+
+ public MessageOnlyLogFilter() {
+ }
+
+ @Override
+ public void println(int priority, String tag, String msg, Throwable tr) {
+ if (mNext != null) {
+ getNext().println(Log.NONE, null, msg, null);
+ }
+ }
+
+ /**
+ * Returns the next LogNode in the chain.
+ */
+ public LogNode getNext() {
+ return mNext;
+ }
+
+ /**
+ * Sets the LogNode data will be sent to..
+ */
+ public void setNext(LogNode node) {
+ mNext = node;
+ }
+
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/provider/Images.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/provider/Images.java
new file mode 100644
index 0000000..5a89546
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/provider/Images.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.displayingbitmaps.provider;
+
+/**
+ * Some simple test data to use for this sample app.
+ */
+public class Images {
+
+ /**
+ * This are PicasaWeb URLs and could potentially change. Ideally the PicasaWeb API should be
+ * used to fetch the URLs.
+ *
+ * Credit to Romain Guy for the photos:
+ * http://www.curious-creature.org/
+ * https://plus.google.com/109538161516040592207/about
+ * http://www.flickr.com/photos/romainguy
+ */
+ public final static String[] imageUrls = new String[] {
+ "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg",
+ "https://lh4.googleusercontent.com/--dq8niRp7W4/URquVgmXvgI/AAAAAAAAAbs/-gnuLQfNnBA/s1024/A%252520Song%252520of%252520Ice%252520and%252520Fire.jpg",
+ "https://lh5.googleusercontent.com/-7qZeDtRKFKc/URquWZT1gOI/AAAAAAAAAbs/hqWgteyNXsg/s1024/Another%252520Rockaway%252520Sunset.jpg",
+ "https://lh3.googleusercontent.com/--L0Km39l5J8/URquXHGcdNI/AAAAAAAAAbs/3ZrSJNrSomQ/s1024/Antelope%252520Butte.jpg",
+ "https://lh6.googleusercontent.com/-8HO-4vIFnlw/URquZnsFgtI/AAAAAAAAAbs/WT8jViTF7vw/s1024/Antelope%252520Hallway.jpg",
+ "https://lh4.googleusercontent.com/-WIuWgVcU3Qw/URqubRVcj4I/AAAAAAAAAbs/YvbwgGjwdIQ/s1024/Antelope%252520Walls.jpg",
+ "https://lh6.googleusercontent.com/-UBmLbPELvoQ/URqucCdv0kI/AAAAAAAAAbs/IdNhr2VQoQs/s1024/Apre%2525CC%252580s%252520la%252520Pluie.jpg",
+ "https://lh3.googleusercontent.com/-s-AFpvgSeew/URquc6dF-JI/AAAAAAAAAbs/Mt3xNGRUd68/s1024/Backlit%252520Cloud.jpg",
+ "https://lh5.googleusercontent.com/-bvmif9a9YOQ/URquea3heHI/AAAAAAAAAbs/rcr6wyeQtAo/s1024/Bee%252520and%252520Flower.jpg",
+ "https://lh5.googleusercontent.com/-n7mdm7I7FGs/URqueT_BT-I/AAAAAAAAAbs/9MYmXlmpSAo/s1024/Bonzai%252520Rock%252520Sunset.jpg",
+ "https://lh6.googleusercontent.com/-4CN4X4t0M1k/URqufPozWzI/AAAAAAAAAbs/8wK41lg1KPs/s1024/Caterpillar.jpg",
+ "https://lh3.googleusercontent.com/-rrFnVC8xQEg/URqufdrLBaI/AAAAAAAAAbs/s69WYy_fl1E/s1024/Chess.jpg",
+ "https://lh5.googleusercontent.com/-WVpRptWH8Yw/URqugh-QmDI/AAAAAAAAAbs/E-MgBgtlUWU/s1024/Chihuly.jpg",
+ "https://lh5.googleusercontent.com/-0BDXkYmckbo/URquhKFW84I/AAAAAAAAAbs/ogQtHCTk2JQ/s1024/Closed%252520Door.jpg",
+ "https://lh3.googleusercontent.com/-PyggXXZRykM/URquh-kVvoI/AAAAAAAAAbs/hFtDwhtrHHQ/s1024/Colorado%252520River%252520Sunset.jpg",
+ "https://lh3.googleusercontent.com/-ZAs4dNZtALc/URquikvOCWI/AAAAAAAAAbs/DXz4h3dll1Y/s1024/Colors%252520of%252520Autumn.jpg",
+ "https://lh4.googleusercontent.com/-GztnWEIiMz8/URqukVCU7bI/AAAAAAAAAbs/jo2Hjv6MZ6M/s1024/Countryside.jpg",
+ "https://lh4.googleusercontent.com/-bEg9EZ9QoiM/URquklz3FGI/AAAAAAAAAbs/UUuv8Ac2BaE/s1024/Death%252520Valley%252520-%252520Dunes.jpg",
+ "https://lh6.googleusercontent.com/-ijQJ8W68tEE/URqulGkvFEI/AAAAAAAAAbs/zPXvIwi_rFw/s1024/Delicate%252520Arch.jpg",
+ "https://lh5.googleusercontent.com/-Oh8mMy2ieng/URqullDwehI/AAAAAAAAAbs/TbdeEfsaIZY/s1024/Despair.jpg",
+ "https://lh5.googleusercontent.com/-gl0y4UiAOlk/URqumC_KjBI/AAAAAAAAAbs/PM1eT7dn4oo/s1024/Eagle%252520Fall%252520Sunrise.jpg",
+ "https://lh3.googleusercontent.com/-hYYHd2_vXPQ/URqumtJa9eI/AAAAAAAAAbs/wAalXVkbSh0/s1024/Electric%252520Storm.jpg",
+ "https://lh5.googleusercontent.com/-PyY_yiyjPTo/URqunUOhHFI/AAAAAAAAAbs/azZoULNuJXc/s1024/False%252520Kiva.jpg",
+ "https://lh6.googleusercontent.com/-PYvLVdvXywk/URqunwd8hfI/AAAAAAAAAbs/qiMwgkFvf6I/s1024/Fitzgerald%252520Streaks.jpg",
+ "https://lh4.googleusercontent.com/-KIR_UobIIqY/URquoCZ9SlI/AAAAAAAAAbs/Y4d4q8sXu4c/s1024/Foggy%252520Sunset.jpg",
+ "https://lh6.googleusercontent.com/-9lzOk_OWZH0/URquoo4xYoI/AAAAAAAAAbs/AwgzHtNVCwU/s1024/Frantic.jpg",
+ "https://lh3.googleusercontent.com/-0X3JNaKaz48/URqupH78wpI/AAAAAAAAAbs/lHXxu_zbH8s/s1024/Golden%252520Gate%252520Afternoon.jpg",
+ "https://lh6.googleusercontent.com/-95sb5ag7ABc/URqupl95RDI/AAAAAAAAAbs/g73R20iVTRA/s1024/Golden%252520Gate%252520Fog.jpg",
+ "https://lh3.googleusercontent.com/-JB9v6rtgHhk/URqup21F-zI/AAAAAAAAAbs/64Fb8qMZWXk/s1024/Golden%252520Grass.jpg",
+ "https://lh4.googleusercontent.com/-EIBGfnuLtII/URquqVHwaRI/AAAAAAAAAbs/FA4McV2u8VE/s1024/Grand%252520Teton.jpg",
+ "https://lh4.googleusercontent.com/-WoMxZvmN9nY/URquq1v2AoI/AAAAAAAAAbs/grj5uMhL6NA/s1024/Grass%252520Closeup.jpg",
+ "https://lh3.googleusercontent.com/-6hZiEHXx64Q/URqurxvNdqI/AAAAAAAAAbs/kWMXM3o5OVI/s1024/Green%252520Grass.jpg",
+ "https://lh5.googleusercontent.com/-6LVb9OXtQ60/URquteBFuKI/AAAAAAAAAbs/4F4kRgecwFs/s1024/Hanging%252520Leaf.jpg",
+ "https://lh4.googleusercontent.com/-zAvf__52ONk/URqutT_IuxI/AAAAAAAAAbs/D_bcuc0thoU/s1024/Highway%2525201.jpg",
+ "https://lh6.googleusercontent.com/-H4SrUg615rA/URquuL27fXI/AAAAAAAAAbs/4aEqJfiMsOU/s1024/Horseshoe%252520Bend%252520Sunset.jpg",
+ "https://lh4.googleusercontent.com/-JhFi4fb_Pqw/URquuX-QXbI/AAAAAAAAAbs/IXpYUxuweYM/s1024/Horseshoe%252520Bend.jpg",
+ "https://lh5.googleusercontent.com/-UGgssvFRJ7g/URquueyJzGI/AAAAAAAAAbs/yYIBlLT0toM/s1024/Into%252520the%252520Blue.jpg",
+ "https://lh3.googleusercontent.com/-CH7KoupI7uI/URquu0FF__I/AAAAAAAAAbs/R7GDmI7v_G0/s1024/Jelly%252520Fish%2525202.jpg",
+ "https://lh4.googleusercontent.com/-pwuuw6yhg8U/URquvPxR3FI/AAAAAAAAAbs/VNGk6f-tsGE/s1024/Jelly%252520Fish%2525203.jpg",
+ "https://lh5.googleusercontent.com/-GoUQVw1fnFw/URquv6xbC0I/AAAAAAAAAbs/zEUVTQQ43Zc/s1024/Kauai.jpg",
+ "https://lh6.googleusercontent.com/-8QdYYQEpYjw/URquwvdh88I/AAAAAAAAAbs/cktDy-ysfHo/s1024/Kyoto%252520Sunset.jpg",
+ "https://lh4.googleusercontent.com/-vPeekyDjOE0/URquwzJ28qI/AAAAAAAAAbs/qxcyXULsZrg/s1024/Lake%252520Tahoe%252520Colors.jpg",
+ "https://lh4.googleusercontent.com/-xBPxWpD4yxU/URquxWHk8AI/AAAAAAAAAbs/ARDPeDYPiMY/s1024/Lava%252520from%252520the%252520Sky.jpg",
+ "https://lh3.googleusercontent.com/-897VXrJB6RE/URquxxxd-5I/AAAAAAAAAbs/j-Cz4T4YvIw/s1024/Leica%25252050mm%252520Summilux.jpg",
+ "https://lh5.googleusercontent.com/-qSJ4D4iXzGo/URquyDWiJ1I/AAAAAAAAAbs/k2pBXeWehOA/s1024/Leica%25252050mm%252520Summilux.jpg",
+ "https://lh6.googleusercontent.com/-dwlPg83vzLg/URquylTVuFI/AAAAAAAAAbs/G6SyQ8b4YsI/s1024/Leica%252520M8%252520%252528Front%252529.jpg",
+ "https://lh3.googleusercontent.com/-R3_EYAyJvfk/URquzQBv8eI/AAAAAAAAAbs/b9xhpUM3pEI/s1024/Light%252520to%252520Sand.jpg",
+ "https://lh3.googleusercontent.com/-fHY5h67QPi0/URqu0Cp4J1I/AAAAAAAAAbs/0lG6m94Z6vM/s1024/Little%252520Bit%252520of%252520Paradise.jpg",
+ "https://lh5.googleusercontent.com/-TzF_LwrCnRM/URqu0RddPOI/AAAAAAAAAbs/gaj2dLiuX0s/s1024/Lone%252520Pine%252520Sunset.jpg",
+ "https://lh3.googleusercontent.com/-4HdpJ4_DXU4/URqu046dJ9I/AAAAAAAAAbs/eBOodtk2_uk/s1024/Lonely%252520Rock.jpg",
+ "https://lh6.googleusercontent.com/-erbF--z-W4s/URqu1ajSLkI/AAAAAAAAAbs/xjDCDO1INzM/s1024/Longue%252520Vue.jpg",
+ "https://lh6.googleusercontent.com/-0CXJRdJaqvc/URqu1opNZNI/AAAAAAAAAbs/PFB2oPUU7Lk/s1024/Look%252520Me%252520in%252520the%252520Eye.jpg",
+ "https://lh3.googleusercontent.com/-D_5lNxnDN6g/URqu2Tk7HVI/AAAAAAAAAbs/p0ddca9W__Y/s1024/Lost%252520in%252520a%252520Field.jpg",
+ "https://lh6.googleusercontent.com/-flsqwMrIk2Q/URqu24PcmjI/AAAAAAAAAbs/5ocIH85XofM/s1024/Marshall%252520Beach%252520Sunset.jpg",
+ "https://lh4.googleusercontent.com/-Y4lgryEVTmU/URqu28kG3gI/AAAAAAAAAbs/OjXpekqtbJ4/s1024/Mono%252520Lake%252520Blue.jpg",
+ "https://lh4.googleusercontent.com/-AaHAJPmcGYA/URqu3PIldHI/AAAAAAAAAbs/lcTqk1SIcRs/s1024/Monument%252520Valley%252520Overlook.jpg",
+ "https://lh4.googleusercontent.com/-vKxfdQ83dQA/URqu31Yq_BI/AAAAAAAAAbs/OUoGk_2AyfM/s1024/Moving%252520Rock.jpg",
+ "https://lh5.googleusercontent.com/-CG62QiPpWXg/URqu4ia4vRI/AAAAAAAAAbs/0YOdqLAlcAc/s1024/Napali%252520Coast.jpg",
+ "https://lh6.googleusercontent.com/-wdGrP5PMmJQ/URqu5PZvn7I/AAAAAAAAAbs/m0abEcdPXe4/s1024/One%252520Wheel.jpg",
+ "https://lh6.googleusercontent.com/-6WS5DoCGuOA/URqu5qx1UgI/AAAAAAAAAbs/giMw2ixPvrY/s1024/Open%252520Sky.jpg",
+ "https://lh6.googleusercontent.com/-u8EHKj8G8GQ/URqu55sM6yI/AAAAAAAAAbs/lIXX_GlTdmI/s1024/Orange%252520Sunset.jpg",
+ "https://lh6.googleusercontent.com/-74Z5qj4bTDE/URqu6LSrJrI/AAAAAAAAAbs/XzmVkw90szQ/s1024/Orchid.jpg",
+ "https://lh6.googleusercontent.com/-lEQE4h6TePE/URqu6t_lSkI/AAAAAAAAAbs/zvGYKOea_qY/s1024/Over%252520there.jpg",
+ "https://lh5.googleusercontent.com/-cauH-53JH2M/URqu66v_USI/AAAAAAAAAbs/EucwwqclfKQ/s1024/Plumes.jpg",
+ "https://lh3.googleusercontent.com/-eDLT2jHDoy4/URqu7axzkAI/AAAAAAAAAbs/iVZE-xJ7lZs/s1024/Rainbokeh.jpg",
+ "https://lh5.googleusercontent.com/-j1NLqEFIyco/URqu8L1CGcI/AAAAAAAAAbs/aqZkgX66zlI/s1024/Rainbow.jpg",
+ "https://lh5.googleusercontent.com/-DRnqmK0t4VU/URqu8XYN9yI/AAAAAAAAAbs/LgvF_592WLU/s1024/Rice%252520Fields.jpg",
+ "https://lh3.googleusercontent.com/-hwh1v3EOGcQ/URqu8qOaKwI/AAAAAAAAAbs/IljRJRnbJGw/s1024/Rockaway%252520Fire%252520Sky.jpg",
+ "https://lh5.googleusercontent.com/-wjV6FQk7tlk/URqu9jCQ8sI/AAAAAAAAAbs/RyYUpdo-c9o/s1024/Rockaway%252520Flow.jpg",
+ "https://lh6.googleusercontent.com/-6cAXNfo7D20/URqu-BdzgPI/AAAAAAAAAbs/OmsYllzJqwo/s1024/Rockaway%252520Sunset%252520Sky.jpg",
+ "https://lh3.googleusercontent.com/-sl8fpGPS-RE/URqu_BOkfgI/AAAAAAAAAbs/Dg2Fv-JxOeg/s1024/Russian%252520Ridge%252520Sunset.jpg",
+ "https://lh6.googleusercontent.com/-gVtY36mMBIg/URqu_q91lkI/AAAAAAAAAbs/3CiFMBcy5MA/s1024/Rust%252520Knot.jpg",
+ "https://lh6.googleusercontent.com/-GHeImuHqJBE/URqu_FKfVLI/AAAAAAAAAbs/axuEJeqam7Q/s1024/Sailing%252520Stones.jpg",
+ "https://lh3.googleusercontent.com/-hBbYZjTOwGc/URqu_ycpIrI/AAAAAAAAAbs/nAdJUXnGJYE/s1024/Seahorse.jpg",
+ "https://lh3.googleusercontent.com/-Iwi6-i6IexY/URqvAYZHsVI/AAAAAAAAAbs/5ETWl4qXsFE/s1024/Shinjuku%252520Street.jpg",
+ "https://lh6.googleusercontent.com/-amhnySTM_MY/URqvAlb5KoI/AAAAAAAAAbs/pFCFgzlKsn0/s1024/Sierra%252520Heavens.jpg",
+ "https://lh5.googleusercontent.com/-dJgjepFrYSo/URqvBVJZrAI/AAAAAAAAAbs/v-F5QWpYO6s/s1024/Sierra%252520Sunset.jpg",
+ "https://lh4.googleusercontent.com/-Z4zGiC5nWdc/URqvBdEwivI/AAAAAAAAAbs/ZRZR1VJ84QA/s1024/Sin%252520Lights.jpg",
+ "https://lh4.googleusercontent.com/-_0cYiWW8ccY/URqvBz3iM4I/AAAAAAAAAbs/9N_Wq8MhLTY/s1024/Starry%252520Lake.jpg",
+ "https://lh3.googleusercontent.com/-A9LMoRyuQUA/URqvCYx_JoI/AAAAAAAAAbs/s7sde1Bz9cI/s1024/Starry%252520Night.jpg",
+ "https://lh3.googleusercontent.com/-KtLJ3k858eY/URqvC_2h_bI/AAAAAAAAAbs/zzEBImwDA_g/s1024/Stream.jpg",
+ "https://lh5.googleusercontent.com/-dFB7Lad6RcA/URqvDUftwWI/AAAAAAAAAbs/BrhoUtXTN7o/s1024/Strip%252520Sunset.jpg",
+ "https://lh5.googleusercontent.com/-at6apgFiN20/URqvDyffUZI/AAAAAAAAAbs/clABCx171bE/s1024/Sunset%252520Hills.jpg",
+ "https://lh4.googleusercontent.com/-7-EHhtQthII/URqvEYTk4vI/AAAAAAAAAbs/QSJZoB3YjVg/s1024/Tenaya%252520Lake%2525202.jpg",
+ "https://lh6.googleusercontent.com/-8MrjV_a-Pok/URqvFC5repI/AAAAAAAAAbs/9inKTg9fbCE/s1024/Tenaya%252520Lake.jpg",
+ "https://lh5.googleusercontent.com/-B1HW-z4zwao/URqvFWYRwUI/AAAAAAAAAbs/8Peli53Bs8I/s1024/The%252520Cave%252520BW.jpg",
+ "https://lh3.googleusercontent.com/-PO4E-xZKAnQ/URqvGRqjYkI/AAAAAAAAAbs/42nyADFsXag/s1024/The%252520Fisherman.jpg",
+ "https://lh4.googleusercontent.com/-iLyZlzfdy7s/URqvG0YScdI/AAAAAAAAAbs/1J9eDKmkXtk/s1024/The%252520Night%252520is%252520Coming.jpg",
+ "https://lh6.googleusercontent.com/-G-k7YkkUco0/URqvHhah6fI/AAAAAAAAAbs/_taQQG7t0vo/s1024/The%252520Road.jpg",
+ "https://lh6.googleusercontent.com/-h-ALJt7kSus/URqvIThqYfI/AAAAAAAAAbs/ejiv35olWS8/s1024/Tokyo%252520Heights.jpg",
+ "https://lh5.googleusercontent.com/-Hy9k-TbS7xg/URqvIjQMOxI/AAAAAAAAAbs/RSpmmOATSkg/s1024/Tokyo%252520Highway.jpg",
+ "https://lh6.googleusercontent.com/-83oOvMb4OZs/URqvJL0T7lI/AAAAAAAAAbs/c5TECZ6RONM/s1024/Tokyo%252520Smog.jpg",
+ "https://lh3.googleusercontent.com/-FB-jfgREEfI/URqvJI3EXAI/AAAAAAAAAbs/XfyweiRF4v8/s1024/Tufa%252520at%252520Night.jpg",
+ "https://lh4.googleusercontent.com/-vngKD5Z1U8w/URqvJUCEgPI/AAAAAAAAAbs/ulxCMVcU6EU/s1024/Valley%252520Sunset.jpg",
+ "https://lh6.googleusercontent.com/-DOz5I2E2oMQ/URqvKMND1kI/AAAAAAAAAbs/Iqf0IsInleo/s1024/Windmill%252520Sunrise.jpg",
+ "https://lh5.googleusercontent.com/-biyiyWcJ9MU/URqvKculiAI/AAAAAAAAAbs/jyPsCplJOpE/s1024/Windmill.jpg",
+ "https://lh4.googleusercontent.com/-PDT167_xRdA/URqvK36mLcI/AAAAAAAAAbs/oi2ik9QseMI/s1024/Windmills.jpg",
+ "https://lh5.googleusercontent.com/-kI_QdYx7VlU/URqvLXCB6gI/AAAAAAAAAbs/N31vlZ6u89o/s1024/Yet%252520Another%252520Rockaway%252520Sunset.jpg",
+ "https://lh4.googleusercontent.com/-e9NHZ5k5MSs/URqvMIBZjtI/AAAAAAAAAbs/1fV810rDNfQ/s1024/Yosemite%252520Tree.jpg",
+ };
+
+ /**
+ * This are PicasaWeb thumbnail URLs and could potentially change. Ideally the PicasaWeb API
+ * should be used to fetch the URLs.
+ *
+ * Credit to Romain Guy for the photos:
+ * http://www.curious-creature.org/
+ * https://plus.google.com/109538161516040592207/about
+ * http://www.flickr.com/photos/romainguy
+ */
+ public final static String[] imageThumbUrls = new String[] {
+ "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s240-c/A%252520Photographer.jpg",
+ "https://lh4.googleusercontent.com/--dq8niRp7W4/URquVgmXvgI/AAAAAAAAAbs/-gnuLQfNnBA/s240-c/A%252520Song%252520of%252520Ice%252520and%252520Fire.jpg",
+ "https://lh5.googleusercontent.com/-7qZeDtRKFKc/URquWZT1gOI/AAAAAAAAAbs/hqWgteyNXsg/s240-c/Another%252520Rockaway%252520Sunset.jpg",
+ "https://lh3.googleusercontent.com/--L0Km39l5J8/URquXHGcdNI/AAAAAAAAAbs/3ZrSJNrSomQ/s240-c/Antelope%252520Butte.jpg",
+ "https://lh6.googleusercontent.com/-8HO-4vIFnlw/URquZnsFgtI/AAAAAAAAAbs/WT8jViTF7vw/s240-c/Antelope%252520Hallway.jpg",
+ "https://lh4.googleusercontent.com/-WIuWgVcU3Qw/URqubRVcj4I/AAAAAAAAAbs/YvbwgGjwdIQ/s240-c/Antelope%252520Walls.jpg",
+ "https://lh6.googleusercontent.com/-UBmLbPELvoQ/URqucCdv0kI/AAAAAAAAAbs/IdNhr2VQoQs/s240-c/Apre%2525CC%252580s%252520la%252520Pluie.jpg",
+ "https://lh3.googleusercontent.com/-s-AFpvgSeew/URquc6dF-JI/AAAAAAAAAbs/Mt3xNGRUd68/s240-c/Backlit%252520Cloud.jpg",
+ "https://lh5.googleusercontent.com/-bvmif9a9YOQ/URquea3heHI/AAAAAAAAAbs/rcr6wyeQtAo/s240-c/Bee%252520and%252520Flower.jpg",
+ "https://lh5.googleusercontent.com/-n7mdm7I7FGs/URqueT_BT-I/AAAAAAAAAbs/9MYmXlmpSAo/s240-c/Bonzai%252520Rock%252520Sunset.jpg",
+ "https://lh6.googleusercontent.com/-4CN4X4t0M1k/URqufPozWzI/AAAAAAAAAbs/8wK41lg1KPs/s240-c/Caterpillar.jpg",
+ "https://lh3.googleusercontent.com/-rrFnVC8xQEg/URqufdrLBaI/AAAAAAAAAbs/s69WYy_fl1E/s240-c/Chess.jpg",
+ "https://lh5.googleusercontent.com/-WVpRptWH8Yw/URqugh-QmDI/AAAAAAAAAbs/E-MgBgtlUWU/s240-c/Chihuly.jpg",
+ "https://lh5.googleusercontent.com/-0BDXkYmckbo/URquhKFW84I/AAAAAAAAAbs/ogQtHCTk2JQ/s240-c/Closed%252520Door.jpg",
+ "https://lh3.googleusercontent.com/-PyggXXZRykM/URquh-kVvoI/AAAAAAAAAbs/hFtDwhtrHHQ/s240-c/Colorado%252520River%252520Sunset.jpg",
+ "https://lh3.googleusercontent.com/-ZAs4dNZtALc/URquikvOCWI/AAAAAAAAAbs/DXz4h3dll1Y/s240-c/Colors%252520of%252520Autumn.jpg",
+ "https://lh4.googleusercontent.com/-GztnWEIiMz8/URqukVCU7bI/AAAAAAAAAbs/jo2Hjv6MZ6M/s240-c/Countryside.jpg",
+ "https://lh4.googleusercontent.com/-bEg9EZ9QoiM/URquklz3FGI/AAAAAAAAAbs/UUuv8Ac2BaE/s240-c/Death%252520Valley%252520-%252520Dunes.jpg",
+ "https://lh6.googleusercontent.com/-ijQJ8W68tEE/URqulGkvFEI/AAAAAAAAAbs/zPXvIwi_rFw/s240-c/Delicate%252520Arch.jpg",
+ "https://lh5.googleusercontent.com/-Oh8mMy2ieng/URqullDwehI/AAAAAAAAAbs/TbdeEfsaIZY/s240-c/Despair.jpg",
+ "https://lh5.googleusercontent.com/-gl0y4UiAOlk/URqumC_KjBI/AAAAAAAAAbs/PM1eT7dn4oo/s240-c/Eagle%252520Fall%252520Sunrise.jpg",
+ "https://lh3.googleusercontent.com/-hYYHd2_vXPQ/URqumtJa9eI/AAAAAAAAAbs/wAalXVkbSh0/s240-c/Electric%252520Storm.jpg",
+ "https://lh5.googleusercontent.com/-PyY_yiyjPTo/URqunUOhHFI/AAAAAAAAAbs/azZoULNuJXc/s240-c/False%252520Kiva.jpg",
+ "https://lh6.googleusercontent.com/-PYvLVdvXywk/URqunwd8hfI/AAAAAAAAAbs/qiMwgkFvf6I/s240-c/Fitzgerald%252520Streaks.jpg",
+ "https://lh4.googleusercontent.com/-KIR_UobIIqY/URquoCZ9SlI/AAAAAAAAAbs/Y4d4q8sXu4c/s240-c/Foggy%252520Sunset.jpg",
+ "https://lh6.googleusercontent.com/-9lzOk_OWZH0/URquoo4xYoI/AAAAAAAAAbs/AwgzHtNVCwU/s240-c/Frantic.jpg",
+ "https://lh3.googleusercontent.com/-0X3JNaKaz48/URqupH78wpI/AAAAAAAAAbs/lHXxu_zbH8s/s240-c/Golden%252520Gate%252520Afternoon.jpg",
+ "https://lh6.googleusercontent.com/-95sb5ag7ABc/URqupl95RDI/AAAAAAAAAbs/g73R20iVTRA/s240-c/Golden%252520Gate%252520Fog.jpg",
+ "https://lh3.googleusercontent.com/-JB9v6rtgHhk/URqup21F-zI/AAAAAAAAAbs/64Fb8qMZWXk/s240-c/Golden%252520Grass.jpg",
+ "https://lh4.googleusercontent.com/-EIBGfnuLtII/URquqVHwaRI/AAAAAAAAAbs/FA4McV2u8VE/s240-c/Grand%252520Teton.jpg",
+ "https://lh4.googleusercontent.com/-WoMxZvmN9nY/URquq1v2AoI/AAAAAAAAAbs/grj5uMhL6NA/s240-c/Grass%252520Closeup.jpg",
+ "https://lh3.googleusercontent.com/-6hZiEHXx64Q/URqurxvNdqI/AAAAAAAAAbs/kWMXM3o5OVI/s240-c/Green%252520Grass.jpg",
+ "https://lh5.googleusercontent.com/-6LVb9OXtQ60/URquteBFuKI/AAAAAAAAAbs/4F4kRgecwFs/s240-c/Hanging%252520Leaf.jpg",
+ "https://lh4.googleusercontent.com/-zAvf__52ONk/URqutT_IuxI/AAAAAAAAAbs/D_bcuc0thoU/s240-c/Highway%2525201.jpg",
+ "https://lh6.googleusercontent.com/-H4SrUg615rA/URquuL27fXI/AAAAAAAAAbs/4aEqJfiMsOU/s240-c/Horseshoe%252520Bend%252520Sunset.jpg",
+ "https://lh4.googleusercontent.com/-JhFi4fb_Pqw/URquuX-QXbI/AAAAAAAAAbs/IXpYUxuweYM/s240-c/Horseshoe%252520Bend.jpg",
+ "https://lh5.googleusercontent.com/-UGgssvFRJ7g/URquueyJzGI/AAAAAAAAAbs/yYIBlLT0toM/s240-c/Into%252520the%252520Blue.jpg",
+ "https://lh3.googleusercontent.com/-CH7KoupI7uI/URquu0FF__I/AAAAAAAAAbs/R7GDmI7v_G0/s240-c/Jelly%252520Fish%2525202.jpg",
+ "https://lh4.googleusercontent.com/-pwuuw6yhg8U/URquvPxR3FI/AAAAAAAAAbs/VNGk6f-tsGE/s240-c/Jelly%252520Fish%2525203.jpg",
+ "https://lh5.googleusercontent.com/-GoUQVw1fnFw/URquv6xbC0I/AAAAAAAAAbs/zEUVTQQ43Zc/s240-c/Kauai.jpg",
+ "https://lh6.googleusercontent.com/-8QdYYQEpYjw/URquwvdh88I/AAAAAAAAAbs/cktDy-ysfHo/s240-c/Kyoto%252520Sunset.jpg",
+ "https://lh4.googleusercontent.com/-vPeekyDjOE0/URquwzJ28qI/AAAAAAAAAbs/qxcyXULsZrg/s240-c/Lake%252520Tahoe%252520Colors.jpg",
+ "https://lh4.googleusercontent.com/-xBPxWpD4yxU/URquxWHk8AI/AAAAAAAAAbs/ARDPeDYPiMY/s240-c/Lava%252520from%252520the%252520Sky.jpg",
+ "https://lh3.googleusercontent.com/-897VXrJB6RE/URquxxxd-5I/AAAAAAAAAbs/j-Cz4T4YvIw/s240-c/Leica%25252050mm%252520Summilux.jpg",
+ "https://lh5.googleusercontent.com/-qSJ4D4iXzGo/URquyDWiJ1I/AAAAAAAAAbs/k2pBXeWehOA/s240-c/Leica%25252050mm%252520Summilux.jpg",
+ "https://lh6.googleusercontent.com/-dwlPg83vzLg/URquylTVuFI/AAAAAAAAAbs/G6SyQ8b4YsI/s240-c/Leica%252520M8%252520%252528Front%252529.jpg",
+ "https://lh3.googleusercontent.com/-R3_EYAyJvfk/URquzQBv8eI/AAAAAAAAAbs/b9xhpUM3pEI/s240-c/Light%252520to%252520Sand.jpg",
+ "https://lh3.googleusercontent.com/-fHY5h67QPi0/URqu0Cp4J1I/AAAAAAAAAbs/0lG6m94Z6vM/s240-c/Little%252520Bit%252520of%252520Paradise.jpg",
+ "https://lh5.googleusercontent.com/-TzF_LwrCnRM/URqu0RddPOI/AAAAAAAAAbs/gaj2dLiuX0s/s240-c/Lone%252520Pine%252520Sunset.jpg",
+ "https://lh3.googleusercontent.com/-4HdpJ4_DXU4/URqu046dJ9I/AAAAAAAAAbs/eBOodtk2_uk/s240-c/Lonely%252520Rock.jpg",
+ "https://lh6.googleusercontent.com/-erbF--z-W4s/URqu1ajSLkI/AAAAAAAAAbs/xjDCDO1INzM/s240-c/Longue%252520Vue.jpg",
+ "https://lh6.googleusercontent.com/-0CXJRdJaqvc/URqu1opNZNI/AAAAAAAAAbs/PFB2oPUU7Lk/s240-c/Look%252520Me%252520in%252520the%252520Eye.jpg",
+ "https://lh3.googleusercontent.com/-D_5lNxnDN6g/URqu2Tk7HVI/AAAAAAAAAbs/p0ddca9W__Y/s240-c/Lost%252520in%252520a%252520Field.jpg",
+ "https://lh6.googleusercontent.com/-flsqwMrIk2Q/URqu24PcmjI/AAAAAAAAAbs/5ocIH85XofM/s240-c/Marshall%252520Beach%252520Sunset.jpg",
+ "https://lh4.googleusercontent.com/-Y4lgryEVTmU/URqu28kG3gI/AAAAAAAAAbs/OjXpekqtbJ4/s240-c/Mono%252520Lake%252520Blue.jpg",
+ "https://lh4.googleusercontent.com/-AaHAJPmcGYA/URqu3PIldHI/AAAAAAAAAbs/lcTqk1SIcRs/s240-c/Monument%252520Valley%252520Overlook.jpg",
+ "https://lh4.googleusercontent.com/-vKxfdQ83dQA/URqu31Yq_BI/AAAAAAAAAbs/OUoGk_2AyfM/s240-c/Moving%252520Rock.jpg",
+ "https://lh5.googleusercontent.com/-CG62QiPpWXg/URqu4ia4vRI/AAAAAAAAAbs/0YOdqLAlcAc/s240-c/Napali%252520Coast.jpg",
+ "https://lh6.googleusercontent.com/-wdGrP5PMmJQ/URqu5PZvn7I/AAAAAAAAAbs/m0abEcdPXe4/s240-c/One%252520Wheel.jpg",
+ "https://lh6.googleusercontent.com/-6WS5DoCGuOA/URqu5qx1UgI/AAAAAAAAAbs/giMw2ixPvrY/s240-c/Open%252520Sky.jpg",
+ "https://lh6.googleusercontent.com/-u8EHKj8G8GQ/URqu55sM6yI/AAAAAAAAAbs/lIXX_GlTdmI/s240-c/Orange%252520Sunset.jpg",
+ "https://lh6.googleusercontent.com/-74Z5qj4bTDE/URqu6LSrJrI/AAAAAAAAAbs/XzmVkw90szQ/s240-c/Orchid.jpg",
+ "https://lh6.googleusercontent.com/-lEQE4h6TePE/URqu6t_lSkI/AAAAAAAAAbs/zvGYKOea_qY/s240-c/Over%252520there.jpg",
+ "https://lh5.googleusercontent.com/-cauH-53JH2M/URqu66v_USI/AAAAAAAAAbs/EucwwqclfKQ/s240-c/Plumes.jpg",
+ "https://lh3.googleusercontent.com/-eDLT2jHDoy4/URqu7axzkAI/AAAAAAAAAbs/iVZE-xJ7lZs/s240-c/Rainbokeh.jpg",
+ "https://lh5.googleusercontent.com/-j1NLqEFIyco/URqu8L1CGcI/AAAAAAAAAbs/aqZkgX66zlI/s240-c/Rainbow.jpg",
+ "https://lh5.googleusercontent.com/-DRnqmK0t4VU/URqu8XYN9yI/AAAAAAAAAbs/LgvF_592WLU/s240-c/Rice%252520Fields.jpg",
+ "https://lh3.googleusercontent.com/-hwh1v3EOGcQ/URqu8qOaKwI/AAAAAAAAAbs/IljRJRnbJGw/s240-c/Rockaway%252520Fire%252520Sky.jpg",
+ "https://lh5.googleusercontent.com/-wjV6FQk7tlk/URqu9jCQ8sI/AAAAAAAAAbs/RyYUpdo-c9o/s240-c/Rockaway%252520Flow.jpg",
+ "https://lh6.googleusercontent.com/-6cAXNfo7D20/URqu-BdzgPI/AAAAAAAAAbs/OmsYllzJqwo/s240-c/Rockaway%252520Sunset%252520Sky.jpg",
+ "https://lh3.googleusercontent.com/-sl8fpGPS-RE/URqu_BOkfgI/AAAAAAAAAbs/Dg2Fv-JxOeg/s240-c/Russian%252520Ridge%252520Sunset.jpg",
+ "https://lh6.googleusercontent.com/-gVtY36mMBIg/URqu_q91lkI/AAAAAAAAAbs/3CiFMBcy5MA/s240-c/Rust%252520Knot.jpg",
+ "https://lh6.googleusercontent.com/-GHeImuHqJBE/URqu_FKfVLI/AAAAAAAAAbs/axuEJeqam7Q/s240-c/Sailing%252520Stones.jpg",
+ "https://lh3.googleusercontent.com/-hBbYZjTOwGc/URqu_ycpIrI/AAAAAAAAAbs/nAdJUXnGJYE/s240-c/Seahorse.jpg",
+ "https://lh3.googleusercontent.com/-Iwi6-i6IexY/URqvAYZHsVI/AAAAAAAAAbs/5ETWl4qXsFE/s240-c/Shinjuku%252520Street.jpg",
+ "https://lh6.googleusercontent.com/-amhnySTM_MY/URqvAlb5KoI/AAAAAAAAAbs/pFCFgzlKsn0/s240-c/Sierra%252520Heavens.jpg",
+ "https://lh5.googleusercontent.com/-dJgjepFrYSo/URqvBVJZrAI/AAAAAAAAAbs/v-F5QWpYO6s/s240-c/Sierra%252520Sunset.jpg",
+ "https://lh4.googleusercontent.com/-Z4zGiC5nWdc/URqvBdEwivI/AAAAAAAAAbs/ZRZR1VJ84QA/s240-c/Sin%252520Lights.jpg",
+ "https://lh4.googleusercontent.com/-_0cYiWW8ccY/URqvBz3iM4I/AAAAAAAAAbs/9N_Wq8MhLTY/s240-c/Starry%252520Lake.jpg",
+ "https://lh3.googleusercontent.com/-A9LMoRyuQUA/URqvCYx_JoI/AAAAAAAAAbs/s7sde1Bz9cI/s240-c/Starry%252520Night.jpg",
+ "https://lh3.googleusercontent.com/-KtLJ3k858eY/URqvC_2h_bI/AAAAAAAAAbs/zzEBImwDA_g/s240-c/Stream.jpg",
+ "https://lh5.googleusercontent.com/-dFB7Lad6RcA/URqvDUftwWI/AAAAAAAAAbs/BrhoUtXTN7o/s240-c/Strip%252520Sunset.jpg",
+ "https://lh5.googleusercontent.com/-at6apgFiN20/URqvDyffUZI/AAAAAAAAAbs/clABCx171bE/s240-c/Sunset%252520Hills.jpg",
+ "https://lh4.googleusercontent.com/-7-EHhtQthII/URqvEYTk4vI/AAAAAAAAAbs/QSJZoB3YjVg/s240-c/Tenaya%252520Lake%2525202.jpg",
+ "https://lh6.googleusercontent.com/-8MrjV_a-Pok/URqvFC5repI/AAAAAAAAAbs/9inKTg9fbCE/s240-c/Tenaya%252520Lake.jpg",
+ "https://lh5.googleusercontent.com/-B1HW-z4zwao/URqvFWYRwUI/AAAAAAAAAbs/8Peli53Bs8I/s240-c/The%252520Cave%252520BW.jpg",
+ "https://lh3.googleusercontent.com/-PO4E-xZKAnQ/URqvGRqjYkI/AAAAAAAAAbs/42nyADFsXag/s240-c/The%252520Fisherman.jpg",
+ "https://lh4.googleusercontent.com/-iLyZlzfdy7s/URqvG0YScdI/AAAAAAAAAbs/1J9eDKmkXtk/s240-c/The%252520Night%252520is%252520Coming.jpg",
+ "https://lh6.googleusercontent.com/-G-k7YkkUco0/URqvHhah6fI/AAAAAAAAAbs/_taQQG7t0vo/s240-c/The%252520Road.jpg",
+ "https://lh6.googleusercontent.com/-h-ALJt7kSus/URqvIThqYfI/AAAAAAAAAbs/ejiv35olWS8/s240-c/Tokyo%252520Heights.jpg",
+ "https://lh5.googleusercontent.com/-Hy9k-TbS7xg/URqvIjQMOxI/AAAAAAAAAbs/RSpmmOATSkg/s240-c/Tokyo%252520Highway.jpg",
+ "https://lh6.googleusercontent.com/-83oOvMb4OZs/URqvJL0T7lI/AAAAAAAAAbs/c5TECZ6RONM/s240-c/Tokyo%252520Smog.jpg",
+ "https://lh3.googleusercontent.com/-FB-jfgREEfI/URqvJI3EXAI/AAAAAAAAAbs/XfyweiRF4v8/s240-c/Tufa%252520at%252520Night.jpg",
+ "https://lh4.googleusercontent.com/-vngKD5Z1U8w/URqvJUCEgPI/AAAAAAAAAbs/ulxCMVcU6EU/s240-c/Valley%252520Sunset.jpg",
+ "https://lh6.googleusercontent.com/-DOz5I2E2oMQ/URqvKMND1kI/AAAAAAAAAbs/Iqf0IsInleo/s240-c/Windmill%252520Sunrise.jpg",
+ "https://lh5.googleusercontent.com/-biyiyWcJ9MU/URqvKculiAI/AAAAAAAAAbs/jyPsCplJOpE/s240-c/Windmill.jpg",
+ "https://lh4.googleusercontent.com/-PDT167_xRdA/URqvK36mLcI/AAAAAAAAAbs/oi2ik9QseMI/s240-c/Windmills.jpg",
+ "https://lh5.googleusercontent.com/-kI_QdYx7VlU/URqvLXCB6gI/AAAAAAAAAbs/N31vlZ6u89o/s240-c/Yet%252520Another%252520Rockaway%252520Sunset.jpg",
+ "https://lh4.googleusercontent.com/-e9NHZ5k5MSs/URqvMIBZjtI/AAAAAAAAAbs/1fV810rDNfQ/s240-c/Yosemite%252520Tree.jpg",
+ };
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/ImageDetailActivity.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/ImageDetailActivity.java
new file mode 100644
index 0000000..c2be1ac
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/ImageDetailActivity.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.displayingbitmaps.ui;
+
+import android.annotation.TargetApi;
+import android.app.ActionBar;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.support.v4.app.NavUtils;
+import android.support.v4.view.ViewPager;
+import android.util.DisplayMetrics;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Toast;
+
+import com.example.android.displayingbitmaps.BuildConfig;
+import com.example.android.displayingbitmaps.R;
+import com.example.android.displayingbitmaps.provider.Images;
+import com.example.android.displayingbitmaps.util.ImageCache;
+import com.example.android.displayingbitmaps.util.ImageFetcher;
+import com.example.android.displayingbitmaps.util.Utils;
+
+public class ImageDetailActivity extends FragmentActivity implements OnClickListener {
+ private static final String IMAGE_CACHE_DIR = "images";
+ public static final String EXTRA_IMAGE = "extra_image";
+
+ private ImagePagerAdapter mAdapter;
+ private ImageFetcher mImageFetcher;
+ private ViewPager mPager;
+
+ @TargetApi(VERSION_CODES.HONEYCOMB)
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ if (BuildConfig.DEBUG) {
+ Utils.enableStrictMode();
+ }
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.image_detail_pager);
+
+ // Fetch screen height and width, to use as our max size when loading images as this
+ // activity runs full screen
+ final DisplayMetrics displayMetrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+ final int height = displayMetrics.heightPixels;
+ final int width = displayMetrics.widthPixels;
+
+ // For this sample we'll use half of the longest width to resize our images. As the
+ // image scaling ensures the image is larger than this, we should be left with a
+ // resolution that is appropriate for both portrait and landscape. For best image quality
+ // we shouldn't divide by 2, but this will use more memory and require a larger memory
+ // cache.
+ final int longest = (height > width ? height : width) / 2;
+
+ ImageCache.ImageCacheParams cacheParams =
+ new ImageCache.ImageCacheParams(this, IMAGE_CACHE_DIR);
+ cacheParams.setMemCacheSizePercent(0.25f); // Set memory cache to 25% of app memory
+
+ // The ImageFetcher takes care of loading images into our ImageView children asynchronously
+ mImageFetcher = new ImageFetcher(this, longest);
+ mImageFetcher.addImageCache(getSupportFragmentManager(), cacheParams);
+ mImageFetcher.setImageFadeIn(false);
+
+ // Set up ViewPager and backing adapter
+ mAdapter = new ImagePagerAdapter(getSupportFragmentManager(), Images.imageUrls.length);
+ mPager = (ViewPager) findViewById(R.id.pager);
+ mPager.setAdapter(mAdapter);
+ mPager.setPageMargin((int) getResources().getDimension(R.dimen.horizontal_page_margin));
+ mPager.setOffscreenPageLimit(2);
+
+ // Set up activity to go full screen
+ getWindow().addFlags(LayoutParams.FLAG_FULLSCREEN);
+
+ // Enable some additional newer visibility and ActionBar features to create a more
+ // immersive photo viewing experience
+ if (Utils.hasHoneycomb()) {
+ final ActionBar actionBar = getActionBar();
+
+ // Hide title text and set home as up
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+
+ // Hide and show the ActionBar as the visibility changes
+ mPager.setOnSystemUiVisibilityChangeListener(
+ new View.OnSystemUiVisibilityChangeListener() {
+ @Override
+ public void onSystemUiVisibilityChange(int vis) {
+ if ((vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
+ actionBar.hide();
+ } else {
+ actionBar.show();
+ }
+ }
+ });
+
+ // Start low profile mode and hide ActionBar
+ mPager.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
+ actionBar.hide();
+ }
+
+ // Set the current item based on the extra passed in to this activity
+ final int extraCurrentItem = getIntent().getIntExtra(EXTRA_IMAGE, -1);
+ if (extraCurrentItem != -1) {
+ mPager.setCurrentItem(extraCurrentItem);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mImageFetcher.setExitTasksEarly(false);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mImageFetcher.setExitTasksEarly(true);
+ mImageFetcher.flushCache();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mImageFetcher.closeCache();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ case R.id.clear_cache:
+ mImageFetcher.clearCache();
+ Toast.makeText(
+ this, R.string.clear_cache_complete_toast,Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.main_menu, menu);
+ return true;
+ }
+
+ /**
+ * Called by the ViewPager child fragments to load images via the one ImageFetcher
+ */
+ public ImageFetcher getImageFetcher() {
+ return mImageFetcher;
+ }
+
+ /**
+ * The main adapter that backs the ViewPager. A subclass of FragmentStatePagerAdapter as there
+ * could be a large number of items in the ViewPager and we don't want to retain them all in
+ * memory at once but create/destroy them on the fly.
+ */
+ private class ImagePagerAdapter extends FragmentStatePagerAdapter {
+ private final int mSize;
+
+ public ImagePagerAdapter(FragmentManager fm, int size) {
+ super(fm);
+ mSize = size;
+ }
+
+ @Override
+ public int getCount() {
+ return mSize;
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ return ImageDetailFragment.newInstance(Images.imageUrls[position]);
+ }
+ }
+
+ /**
+ * Set on the ImageView in the ViewPager children fragments, to enable/disable low profile mode
+ * when the ImageView is touched.
+ */
+ @TargetApi(VERSION_CODES.HONEYCOMB)
+ @Override
+ public void onClick(View v) {
+ final int vis = mPager.getSystemUiVisibility();
+ if ((vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
+ mPager.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
+ } else {
+ mPager.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
+ }
+ }
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/ImageDetailFragment.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/ImageDetailFragment.java
new file mode 100644
index 0000000..506729a
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/ImageDetailFragment.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.displayingbitmaps.ui;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.example.android.displayingbitmaps.R;
+import com.example.android.displayingbitmaps.util.ImageFetcher;
+import com.example.android.displayingbitmaps.util.ImageWorker;
+import com.example.android.displayingbitmaps.util.Utils;
+
+/**
+ * This fragment will populate the children of the ViewPager from {@link ImageDetailActivity}.
+ */
+public class ImageDetailFragment extends Fragment {
+ private static final String IMAGE_DATA_EXTRA = "extra_image_data";
+ private String mImageUrl;
+ private ImageView mImageView;
+ private ImageFetcher mImageFetcher;
+
+ /**
+ * Factory method to generate a new instance of the fragment given an image number.
+ *
+ * @param imageUrl The image url to load
+ * @return A new instance of ImageDetailFragment with imageNum extras
+ */
+ public static ImageDetailFragment newInstance(String imageUrl) {
+ final ImageDetailFragment f = new ImageDetailFragment();
+
+ final Bundle args = new Bundle();
+ args.putString(IMAGE_DATA_EXTRA, imageUrl);
+ f.setArguments(args);
+
+ return f;
+ }
+
+ /**
+ * Empty constructor as per the Fragment documentation
+ */
+ public ImageDetailFragment() {}
+
+ /**
+ * Populate image using a url from extras, use the convenience factory method
+ * {@link ImageDetailFragment#newInstance(String)} to create this fragment.
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mImageUrl = getArguments() != null ? getArguments().getString(IMAGE_DATA_EXTRA) : null;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate and locate the main ImageView
+ final View v = inflater.inflate(R.layout.image_detail_fragment, container, false);
+ mImageView = (ImageView) v.findViewById(R.id.imageView);
+ return v;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ // Use the parent activity to load the image asynchronously into the ImageView (so a single
+ // cache can be used over all pages in the ViewPager
+ if (ImageDetailActivity.class.isInstance(getActivity())) {
+ mImageFetcher = ((ImageDetailActivity) getActivity()).getImageFetcher();
+ mImageFetcher.loadImage(mImageUrl, mImageView);
+ }
+
+ // Pass clicks on the ImageView to the parent activity to handle
+ if (OnClickListener.class.isInstance(getActivity()) && Utils.hasHoneycomb()) {
+ mImageView.setOnClickListener((OnClickListener) getActivity());
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mImageView != null) {
+ // Cancel any pending image work
+ ImageWorker.cancelWork(mImageView);
+ mImageView.setImageDrawable(null);
+ }
+ }
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/ImageGridActivity.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/ImageGridActivity.java
new file mode 100644
index 0000000..f171955
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/ImageGridActivity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.displayingbitmaps.ui;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+
+import com.example.android.displayingbitmaps.BuildConfig;
+import com.example.android.displayingbitmaps.util.Utils;
+
+/**
+ * Simple FragmentActivity to hold the main {@link ImageGridFragment} and not much else.
+ */
+public class ImageGridActivity extends FragmentActivity {
+ private static final String TAG = "ImageGridActivity";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ if (BuildConfig.DEBUG) {
+ Utils.enableStrictMode();
+ }
+ super.onCreate(savedInstanceState);
+
+ if (getSupportFragmentManager().findFragmentByTag(TAG) == null) {
+ final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+ ft.add(android.R.id.content, new ImageGridFragment(), TAG);
+ ft.commit();
+ }
+ }
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/ImageGridFragment.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/ImageGridFragment.java
new file mode 100644
index 0000000..4eb1f1b
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/ImageGridFragment.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.displayingbitmaps.ui;
+
+import android.annotation.TargetApi;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewTreeObserver;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.example.android.common.logger.Log;
+import com.example.android.displayingbitmaps.BuildConfig;
+import com.example.android.displayingbitmaps.R;
+import com.example.android.displayingbitmaps.provider.Images;
+import com.example.android.displayingbitmaps.util.ImageCache;
+import com.example.android.displayingbitmaps.util.ImageFetcher;
+import com.example.android.displayingbitmaps.util.Utils;
+
+/**
+ * The main fragment that powers the ImageGridActivity screen. Fairly straight forward GridView
+ * implementation with the key addition being the ImageWorker class w/ImageCache to load children
+ * asynchronously, keeping the UI nice and smooth and caching thumbnails for quick retrieval. The
+ * cache is retained over configuration changes like orientation change so the images are populated
+ * quickly if, for example, the user rotates the device.
+ */
+public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
+ private static final String TAG = "ImageGridFragment";
+ private static final String IMAGE_CACHE_DIR = "thumbs";
+
+ private int mImageThumbSize;
+ private int mImageThumbSpacing;
+ private ImageAdapter mAdapter;
+ private ImageFetcher mImageFetcher;
+
+ /**
+ * Empty constructor as per the Fragment documentation
+ */
+ public ImageGridFragment() {}
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+
+ mImageThumbSize = getResources().getDimensionPixelSize(R.dimen.image_thumbnail_size);
+ mImageThumbSpacing = getResources().getDimensionPixelSize(R.dimen.image_thumbnail_spacing);
+
+ mAdapter = new ImageAdapter(getActivity());
+
+ ImageCache.ImageCacheParams cacheParams =
+ new ImageCache.ImageCacheParams(getActivity(), IMAGE_CACHE_DIR);
+
+ cacheParams.setMemCacheSizePercent(0.25f); // Set memory cache to 25% of app memory
+
+ // The ImageFetcher takes care of loading images into our ImageView children asynchronously
+ mImageFetcher = new ImageFetcher(getActivity(), mImageThumbSize);
+ mImageFetcher.setLoadingImage(R.drawable.empty_photo);
+ mImageFetcher.addImageCache(getActivity().getSupportFragmentManager(), cacheParams);
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+
+ final View v = inflater.inflate(R.layout.image_grid_fragment, container, false);
+ final GridView mGridView = (GridView) v.findViewById(R.id.gridView);
+ mGridView.setAdapter(mAdapter);
+ mGridView.setOnItemClickListener(this);
+ mGridView.setOnScrollListener(new AbsListView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(AbsListView absListView, int scrollState) {
+ // Pause fetcher to ensure smoother scrolling when flinging
+ if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
+ // Before Honeycomb pause image loading on scroll to help with performance
+ if (!Utils.hasHoneycomb()) {
+ mImageFetcher.setPauseWork(true);
+ }
+ } else {
+ mImageFetcher.setPauseWork(false);
+ }
+ }
+
+ @Override
+ public void onScroll(AbsListView absListView, int firstVisibleItem,
+ int visibleItemCount, int totalItemCount) {
+ }
+ });
+
+ // This listener is used to get the final width of the GridView and then calculate the
+ // number of columns and the width of each column. The width of each column is variable
+ // as the GridView has stretchMode=columnWidth. The column width is used to set the height
+ // of each view so we get nice square thumbnails.
+ mGridView.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @TargetApi(VERSION_CODES.JELLY_BEAN)
+ @Override
+ public void onGlobalLayout() {
+ if (mAdapter.getNumColumns() == 0) {
+ final int numColumns = (int) Math.floor(
+ mGridView.getWidth() / (mImageThumbSize + mImageThumbSpacing));
+ if (numColumns > 0) {
+ final int columnWidth =
+ (mGridView.getWidth() / numColumns) - mImageThumbSpacing;
+ mAdapter.setNumColumns(numColumns);
+ mAdapter.setItemHeight(columnWidth);
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "onCreateView - numColumns set to " + numColumns);
+ }
+ if (Utils.hasJellyBean()) {
+ mGridView.getViewTreeObserver()
+ .removeOnGlobalLayoutListener(this);
+ } else {
+ mGridView.getViewTreeObserver()
+ .removeGlobalOnLayoutListener(this);
+ }
+ }
+ }
+ }
+ });
+
+ return v;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mImageFetcher.setExitTasksEarly(false);
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mImageFetcher.setPauseWork(false);
+ mImageFetcher.setExitTasksEarly(true);
+ mImageFetcher.flushCache();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mImageFetcher.closeCache();
+ }
+
+ @TargetApi(VERSION_CODES.JELLY_BEAN)
+ @Override
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+ final Intent i = new Intent(getActivity(), ImageDetailActivity.class);
+ i.putExtra(ImageDetailActivity.EXTRA_IMAGE, (int) id);
+ if (Utils.hasJellyBean()) {
+ // makeThumbnailScaleUpAnimation() looks kind of ugly here as the loading spinner may
+ // show plus the thumbnail image in GridView is cropped. so using
+ // makeScaleUpAnimation() instead.
+ ActivityOptions options =
+ ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getWidth(), v.getHeight());
+ getActivity().startActivity(i, options.toBundle());
+ } else {
+ startActivity(i);
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.main_menu, menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.clear_cache:
+ mImageFetcher.clearCache();
+ Toast.makeText(getActivity(), R.string.clear_cache_complete_toast,
+ Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * The main adapter that backs the GridView. This is fairly standard except the number of
+ * columns in the GridView is used to create a fake top row of empty views as we use a
+ * transparent ActionBar and don't want the real top row of images to start off covered by it.
+ */
+ private class ImageAdapter extends BaseAdapter {
+
+ private final Context mContext;
+ private int mItemHeight = 0;
+ private int mNumColumns = 0;
+ private int mActionBarHeight = 0;
+ private GridView.LayoutParams mImageViewLayoutParams;
+
+ public ImageAdapter(Context context) {
+ super();
+ mContext = context;
+ mImageViewLayoutParams = new GridView.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ // Calculate ActionBar height
+ TypedValue tv = new TypedValue();
+ if (context.getTheme().resolveAttribute(
+ android.R.attr.actionBarSize, tv, true)) {
+ mActionBarHeight = TypedValue.complexToDimensionPixelSize(
+ tv.data, context.getResources().getDisplayMetrics());
+ }
+ }
+
+ @Override
+ public int getCount() {
+ // If columns have yet to be determined, return no items
+ if (getNumColumns() == 0) {
+ return 0;
+ }
+
+ // Size + number of columns for top empty row
+ return Images.imageThumbUrls.length + mNumColumns;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return position < mNumColumns ?
+ null : Images.imageThumbUrls[position - mNumColumns];
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position < mNumColumns ? 0 : position - mNumColumns;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ // Two types of views, the normal ImageView and the top row of empty views
+ return 2;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return (position < mNumColumns) ? 1 : 0;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup container) {
+ //BEGIN_INCLUDE(load_gridview_item)
+ // First check if this is the top row
+ if (position < mNumColumns) {
+ if (convertView == null) {
+ convertView = new View(mContext);
+ }
+ // Set empty view with height of ActionBar
+ convertView.setLayoutParams(new AbsListView.LayoutParams(
+ LayoutParams.MATCH_PARENT, mActionBarHeight));
+ return convertView;
+ }
+
+ // Now handle the main ImageView thumbnails
+ ImageView imageView;
+ if (convertView == null) { // if it's not recycled, instantiate and initialize
+ imageView = new RecyclingImageView(mContext);
+ imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ imageView.setLayoutParams(mImageViewLayoutParams);
+ } else { // Otherwise re-use the converted view
+ imageView = (ImageView) convertView;
+ }
+
+ // Check the height matches our calculated column width
+ if (imageView.getLayoutParams().height != mItemHeight) {
+ imageView.setLayoutParams(mImageViewLayoutParams);
+ }
+
+ // Finally load the image asynchronously into the ImageView, this also takes care of
+ // setting a placeholder image while the background thread runs
+ mImageFetcher.loadImage(Images.imageThumbUrls[position - mNumColumns], imageView);
+ return imageView;
+ //END_INCLUDE(load_gridview_item)
+ }
+
+ /**
+ * Sets the item height. Useful for when we know the column width so the height can be set
+ * to match.
+ *
+ * @param height
+ */
+ public void setItemHeight(int height) {
+ if (height == mItemHeight) {
+ return;
+ }
+ mItemHeight = height;
+ mImageViewLayoutParams =
+ new GridView.LayoutParams(LayoutParams.MATCH_PARENT, mItemHeight);
+ mImageFetcher.setImageSize(height);
+ notifyDataSetChanged();
+ }
+
+ public void setNumColumns(int numColumns) {
+ mNumColumns = numColumns;
+ }
+
+ public int getNumColumns() {
+ return mNumColumns;
+ }
+ }
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/RecyclingImageView.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/RecyclingImageView.java
new file mode 100644
index 0000000..1db134c
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/ui/RecyclingImageView.java
@@ -0,0 +1,89 @@
+/*
+ * 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 com.example.android.displayingbitmaps.ui;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+import com.example.android.displayingbitmaps.util.RecyclingBitmapDrawable;
+
+/**
+ * Sub-class of ImageView which automatically notifies the drawable when it is
+ * being displayed.
+ */
+public class RecyclingImageView extends ImageView {
+
+ public RecyclingImageView(Context context) {
+ super(context);
+ }
+
+ public RecyclingImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * @see android.widget.ImageView#onDetachedFromWindow()
+ */
+ @Override
+ protected void onDetachedFromWindow() {
+ // This has been detached from Window, so clear the drawable
+ setImageDrawable(null);
+
+ super.onDetachedFromWindow();
+ }
+
+ /**
+ * @see android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)
+ */
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ // Keep hold of previous Drawable
+ final Drawable previousDrawable = getDrawable();
+
+ // Call super to set new Drawable
+ super.setImageDrawable(drawable);
+
+ // Notify new Drawable that it is being displayed
+ notifyDrawable(drawable, true);
+
+ // Notify old Drawable so it is no longer being displayed
+ notifyDrawable(previousDrawable, false);
+ }
+
+ /**
+ * Notifies the drawable that it's displayed state has changed.
+ *
+ * @param drawable
+ * @param isDisplayed
+ */
+ private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) {
+ if (drawable instanceof RecyclingBitmapDrawable) {
+ // The drawable is a CountingBitmapDrawable, so notify it
+ ((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed);
+ } else if (drawable instanceof LayerDrawable) {
+ // The drawable is a LayerDrawable, so recurse on each layer
+ LayerDrawable layerDrawable = (LayerDrawable) drawable;
+ for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) {
+ notifyDrawable(layerDrawable.getDrawable(i), isDisplayed);
+ }
+ }
+ }
+
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/AsyncTask.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/AsyncTask.java
new file mode 100644
index 0000000..dffade4
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/AsyncTask.java
@@ -0,0 +1,693 @@
+/*
+ * Copyright (C) 2008 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 com.example.android.displayingbitmaps.util;
+
+import android.annotation.TargetApi;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Process;
+
+import java.util.ArrayDeque;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * *************************************
+ * Copied from JB release framework:
+ * https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/java/android/os/AsyncTask.java
+ *
+ * so that threading behavior on all OS versions is the same and we can tweak behavior by using
+ * executeOnExecutor() if needed.
+ *
+ * There are 3 changes in this copy of AsyncTask:
+ * -pre-HC a single thread executor is used for serial operation
+ * (Executors.newSingleThreadExecutor) and is the default
+ * -the default THREAD_POOL_EXECUTOR was changed to use DiscardOldestPolicy
+ * -a new fixed thread pool called DUAL_THREAD_EXECUTOR was added
+ * *************************************
+ *
+ * <p>AsyncTask enables proper and easy use of the UI thread. This class allows to
+ * perform background operations and publish results on the UI thread without
+ * having to manipulate threads and/or handlers.</p>
+ *
+ * <p>AsyncTask is designed to be a helper class around {@link Thread} and {@link android.os.Handler}
+ * and does not constitute a generic threading framework. AsyncTasks should ideally be
+ * used for short operations (a few seconds at the most.) If you need to keep threads
+ * running for long periods of time, it is highly recommended you use the various APIs
+ * provided by the <code>java.util.concurrent</code> pacakge such as {@link java.util.concurrent.Executor},
+ * {@link java.util.concurrent.ThreadPoolExecutor} and {@link java.util.concurrent.FutureTask}.</p>
+ *
+ * <p>An asynchronous task is defined by a computation that runs on a background thread and
+ * whose result is published on the UI thread. An asynchronous task is defined by 3 generic
+ * types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>,
+ * and 4 steps, called <code>onPreExecute</code>, <code>doInBackground</code>,
+ * <code>onProgressUpdate</code> and <code>onPostExecute</code>.</p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using tasks and threads, read the
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html">Processes and
+ * Threads</a> developer guide.</p>
+ * </div>
+ *
+ * <h2>Usage</h2>
+ * <p>AsyncTask must be subclassed to be used. The subclass will override at least
+ * one method ({@link #doInBackground}), and most often will override a
+ * second one ({@link #onPostExecute}.)</p>
+ *
+ * <p>Here is an example of subclassing:</p>
+ * <pre class="prettyprint">
+ * private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
+ * protected Long doInBackground(URL... urls) {
+ * int count = urls.length;
+ * long totalSize = 0;
+ * for (int i = 0; i < count; i++) {
+ * totalSize += Downloader.downloadFile(urls[i]);
+ * publishProgress((int) ((i / (float) count) * 100));
+ * // Escape early if cancel() is called
+ * if (isCancelled()) break;
+ * }
+ * return totalSize;
+ * }
+ *
+ * protected void onProgressUpdate(Integer... progress) {
+ * setProgressPercent(progress[0]);
+ * }
+ *
+ * protected void onPostExecute(Long result) {
+ * showDialog("Downloaded " + result + " bytes");
+ * }
+ * }
+ * </pre>
+ *
+ * <p>Once created, a task is executed very simply:</p>
+ * <pre class="prettyprint">
+ * new DownloadFilesTask().execute(url1, url2, url3);
+ * </pre>
+ *
+ * <h2>AsyncTask's generic types</h2>
+ * <p>The three types used by an asynchronous task are the following:</p>
+ * <ol>
+ * <li><code>Params</code>, the type of the parameters sent to the task upon
+ * execution.</li>
+ * <li><code>Progress</code>, the type of the progress units published during
+ * the background computation.</li>
+ * <li><code>Result</code>, the type of the result of the background
+ * computation.</li>
+ * </ol>
+ * <p>Not all types are always used by an asynchronous task. To mark a type as unused,
+ * simply use the type {@link Void}:</p>
+ * <pre>
+ * private class MyTask extends AsyncTask<Void, Void, Void> { ... }
+ * </pre>
+ *
+ * <h2>The 4 steps</h2>
+ * <p>When an asynchronous task is executed, the task goes through 4 steps:</p>
+ * <ol>
+ * <li>{@link #onPreExecute()}, invoked on the UI thread immediately after the task
+ * is executed. This step is normally used to setup the task, for instance by
+ * showing a progress bar in the user interface.</li>
+ * <li>{@link #doInBackground}, invoked on the background thread
+ * immediately after {@link #onPreExecute()} finishes executing. This step is used
+ * to perform background computation that can take a long time. The parameters
+ * of the asynchronous task are passed to this step. The result of the computation must
+ * be returned by this step and will be passed back to the last step. This step
+ * can also use {@link #publishProgress} to publish one or more units
+ * of progress. These values are published on the UI thread, in the
+ * {@link #onProgressUpdate} step.</li>
+ * <li>{@link #onProgressUpdate}, invoked on the UI thread after a
+ * call to {@link #publishProgress}. The timing of the execution is
+ * undefined. This method is used to display any form of progress in the user
+ * interface while the background computation is still executing. For instance,
+ * it can be used to animate a progress bar or show logs in a text field.</li>
+ * <li>{@link #onPostExecute}, invoked on the UI thread after the background
+ * computation finishes. The result of the background computation is passed to
+ * this step as a parameter.</li>
+ * </ol>
+ *
+ * <h2>Cancelling a task</h2>
+ * <p>A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking
+ * this method will cause subsequent calls to {@link #isCancelled()} to return true.
+ * After invoking this method, {@link #onCancelled(Object)}, instead of
+ * {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])}
+ * returns. To ensure that a task is cancelled as quickly as possible, you should always
+ * check the return value of {@link #isCancelled()} periodically from
+ * {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)</p>
+ *
+ * <h2>Threading rules</h2>
+ * <p>There are a few threading rules that must be followed for this class to
+ * work properly:</p>
+ * <ul>
+ * <li>The AsyncTask class must be loaded on the UI thread. This is done
+ * automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.</li>
+ * <li>The task instance must be created on the UI thread.</li>
+ * <li>{@link #execute} must be invoked on the UI thread.</li>
+ * <li>Do not call {@link #onPreExecute()}, {@link #onPostExecute},
+ * {@link #doInBackground}, {@link #onProgressUpdate} manually.</li>
+ * <li>The task can be executed only once (an exception will be thrown if
+ * a second execution is attempted.)</li>
+ * </ul>
+ *
+ * <h2>Memory observability</h2>
+ * <p>AsyncTask guarantees that all callback calls are synchronized in such a way that the following
+ * operations are safe without explicit synchronizations.</p>
+ * <ul>
+ * <li>Set member fields in the constructor or {@link #onPreExecute}, and refer to them
+ * in {@link #doInBackground}.
+ * <li>Set member fields in {@link #doInBackground}, and refer to them in
+ * {@link #onProgressUpdate} and {@link #onPostExecute}.
+ * </ul>
+ *
+ * <h2>Order of execution</h2>
+ * <p>When first introduced, AsyncTasks were executed serially on a single background
+ * thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
+ * to a pool of threads allowing multiple tasks to operate in parallel. Starting with
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single
+ * thread to avoid common application errors caused by parallel execution.</p>
+ * <p>If you truly want parallel execution, you can invoke
+ * {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with
+ * {@link #THREAD_POOL_EXECUTOR}.</p>
+ */
+public abstract class AsyncTask<Params, Progress, Result> {
+ private static final String LOG_TAG = "AsyncTask";
+
+ private static final int CORE_POOL_SIZE = 5;
+ private static final int MAXIMUM_POOL_SIZE = 128;
+ private static final int KEEP_ALIVE = 1;
+
+ private static final ThreadFactory sThreadFactory = new ThreadFactory() {
+ private final AtomicInteger mCount = new AtomicInteger(1);
+
+ public Thread newThread(Runnable r) {
+ return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
+ }
+ };
+
+ private static final BlockingQueue<Runnable> sPoolWorkQueue =
+ new LinkedBlockingQueue<Runnable>(10);
+
+ /**
+ * An {@link java.util.concurrent.Executor} that can be used to execute tasks in parallel.
+ */
+ public static final Executor THREAD_POOL_EXECUTOR
+ = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
+ TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory,
+ new ThreadPoolExecutor.DiscardOldestPolicy());
+
+ /**
+ * An {@link java.util.concurrent.Executor} that executes tasks one at a time in serial
+ * order. This serialization is global to a particular process.
+ */
+ public static final Executor SERIAL_EXECUTOR = Utils.hasHoneycomb() ? new SerialExecutor() :
+ Executors.newSingleThreadExecutor(sThreadFactory);
+
+ public static final Executor DUAL_THREAD_EXECUTOR =
+ Executors.newFixedThreadPool(2, sThreadFactory);
+
+ private static final int MESSAGE_POST_RESULT = 0x1;
+ private static final int MESSAGE_POST_PROGRESS = 0x2;
+
+ private static final InternalHandler sHandler = new InternalHandler();
+
+ private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
+ private final WorkerRunnable<Params, Result> mWorker;
+ private final FutureTask<Result> mFuture;
+
+ private volatile Status mStatus = Status.PENDING;
+
+ private final AtomicBoolean mCancelled = new AtomicBoolean();
+ private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
+
+ @TargetApi(11)
+ private static class SerialExecutor implements Executor {
+ final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
+ Runnable mActive;
+
+ public synchronized void execute(final Runnable r) {
+ mTasks.offer(new Runnable() {
+ public void run() {
+ try {
+ r.run();
+ } finally {
+ scheduleNext();
+ }
+ }
+ });
+ if (mActive == null) {
+ scheduleNext();
+ }
+ }
+
+ protected synchronized void scheduleNext() {
+ if ((mActive = mTasks.poll()) != null) {
+ THREAD_POOL_EXECUTOR.execute(mActive);
+ }
+ }
+ }
+
+ /**
+ * Indicates the current status of the task. Each status will be set only once
+ * during the lifetime of a task.
+ */
+ public enum Status {
+ /**
+ * Indicates that the task has not been executed yet.
+ */
+ PENDING,
+ /**
+ * Indicates that the task is running.
+ */
+ RUNNING,
+ /**
+ * Indicates that {@link AsyncTask#onPostExecute} has finished.
+ */
+ FINISHED,
+ }
+
+ /** @hide Used to force static handler to be created. */
+ public static void init() {
+ sHandler.getLooper();
+ }
+
+ /** @hide */
+ public static void setDefaultExecutor(Executor exec) {
+ sDefaultExecutor = exec;
+ }
+
+ /**
+ * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
+ */
+ public AsyncTask() {
+ mWorker = new WorkerRunnable<Params, Result>() {
+ public Result call() throws Exception {
+ mTaskInvoked.set(true);
+
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ //noinspection unchecked
+ return postResult(doInBackground(mParams));
+ }
+ };
+
+ mFuture = new FutureTask<Result>(mWorker) {
+ @Override
+ protected void done() {
+ try {
+ postResultIfNotInvoked(get());
+ } catch (InterruptedException e) {
+ android.util.Log.w(LOG_TAG, e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException("An error occured while executing doInBackground()",
+ e.getCause());
+ } catch (CancellationException e) {
+ postResultIfNotInvoked(null);
+ }
+ }
+ };
+ }
+
+ private void postResultIfNotInvoked(Result result) {
+ final boolean wasTaskInvoked = mTaskInvoked.get();
+ if (!wasTaskInvoked) {
+ postResult(result);
+ }
+ }
+
+ private Result postResult(Result result) {
+ @SuppressWarnings("unchecked")
+ Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
+ new AsyncTaskResult<Result>(this, result));
+ message.sendToTarget();
+ return result;
+ }
+
+ /**
+ * Returns the current status of this task.
+ *
+ * @return The current status.
+ */
+ public final Status getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Override this method to perform a computation on a background thread. The
+ * specified parameters are the parameters passed to {@link #execute}
+ * by the caller of this task.
+ *
+ * This method can call {@link #publishProgress} to publish updates
+ * on the UI thread.
+ *
+ * @param params The parameters of the task.
+ *
+ * @return A result, defined by the subclass of this task.
+ *
+ * @see #onPreExecute()
+ * @see #onPostExecute
+ * @see #publishProgress
+ */
+ protected abstract Result doInBackground(Params... params);
+
+ /**
+ * Runs on the UI thread before {@link #doInBackground}.
+ *
+ * @see #onPostExecute
+ * @see #doInBackground
+ */
+ protected void onPreExecute() {
+ }
+
+ /**
+ * <p>Runs on the UI thread after {@link #doInBackground}. The
+ * specified result is the value returned by {@link #doInBackground}.</p>
+ *
+ * <p>This method won't be invoked if the task was cancelled.</p>
+ *
+ * @param result The result of the operation computed by {@link #doInBackground}.
+ *
+ * @see #onPreExecute
+ * @see #doInBackground
+ * @see #onCancelled(Object)
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ protected void onPostExecute(Result result) {
+ }
+
+ /**
+ * Runs on the UI thread after {@link #publishProgress} is invoked.
+ * The specified values are the values passed to {@link #publishProgress}.
+ *
+ * @param values The values indicating progress.
+ *
+ * @see #publishProgress
+ * @see #doInBackground
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ protected void onProgressUpdate(Progress... values) {
+ }
+
+ /**
+ * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
+ * {@link #doInBackground(Object[])} has finished.</p>
+ *
+ * <p>The default implementation simply invokes {@link #onCancelled()} and
+ * ignores the result. If you write your own implementation, do not call
+ * <code>super.onCancelled(result)</code>.</p>
+ *
+ * @param result The result, if any, computed in
+ * {@link #doInBackground(Object[])}, can be null
+ *
+ * @see #cancel(boolean)
+ * @see #isCancelled()
+ */
+ @SuppressWarnings({"UnusedParameters"})
+ protected void onCancelled(Result result) {
+ onCancelled();
+ }
+
+ /**
+ * <p>Applications should preferably override {@link #onCancelled(Object)}.
+ * This method is invoked by the default implementation of
+ * {@link #onCancelled(Object)}.</p>
+ *
+ * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
+ * {@link #doInBackground(Object[])} has finished.</p>
+ *
+ * @see #onCancelled(Object)
+ * @see #cancel(boolean)
+ * @see #isCancelled()
+ */
+ protected void onCancelled() {
+ }
+
+ /**
+ * Returns <tt>true</tt> if this task was cancelled before it completed
+ * normally. If you are calling {@link #cancel(boolean)} on the task,
+ * the value returned by this method should be checked periodically from
+ * {@link #doInBackground(Object[])} to end the task as soon as possible.
+ *
+ * @return <tt>true</tt> if task was cancelled before it completed
+ *
+ * @see #cancel(boolean)
+ */
+ public final boolean isCancelled() {
+ return mCancelled.get();
+ }
+
+ /**
+ * <p>Attempts to cancel execution of this task. This attempt will
+ * fail if the task has already completed, already been cancelled,
+ * or could not be cancelled for some other reason. If successful,
+ * and this task has not started when <tt>cancel</tt> is called,
+ * this task should never run. If the task has already started,
+ * then the <tt>mayInterruptIfRunning</tt> parameter determines
+ * whether the thread executing this task should be interrupted in
+ * an attempt to stop the task.</p>
+ *
+ * <p>Calling this method will result in {@link #onCancelled(Object)} being
+ * invoked on the UI thread after {@link #doInBackground(Object[])}
+ * returns. Calling this method guarantees that {@link #onPostExecute(Object)}
+ * is never invoked. After invoking this method, you should check the
+ * value returned by {@link #isCancelled()} periodically from
+ * {@link #doInBackground(Object[])} to finish the task as early as
+ * possible.</p>
+ *
+ * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
+ * task should be interrupted; otherwise, in-progress tasks are allowed
+ * to complete.
+ *
+ * @return <tt>false</tt> if the task could not be cancelled,
+ * typically because it has already completed normally;
+ * <tt>true</tt> otherwise
+ *
+ * @see #isCancelled()
+ * @see #onCancelled(Object)
+ */
+ public final boolean cancel(boolean mayInterruptIfRunning) {
+ mCancelled.set(true);
+ return mFuture.cancel(mayInterruptIfRunning);
+ }
+
+ /**
+ * Waits if necessary for the computation to complete, and then
+ * retrieves its result.
+ *
+ * @return The computed result.
+ *
+ * @throws java.util.concurrent.CancellationException If the computation was cancelled.
+ * @throws java.util.concurrent.ExecutionException If the computation threw an exception.
+ * @throws InterruptedException If the current thread was interrupted
+ * while waiting.
+ */
+ public final Result get() throws InterruptedException, ExecutionException {
+ return mFuture.get();
+ }
+
+ /**
+ * Waits if necessary for at most the given time for the computation
+ * to complete, and then retrieves its result.
+ *
+ * @param timeout Time to wait before cancelling the operation.
+ * @param unit The time unit for the timeout.
+ *
+ * @return The computed result.
+ *
+ * @throws java.util.concurrent.CancellationException If the computation was cancelled.
+ * @throws java.util.concurrent.ExecutionException If the computation threw an exception.
+ * @throws InterruptedException If the current thread was interrupted
+ * while waiting.
+ * @throws java.util.concurrent.TimeoutException If the wait timed out.
+ */
+ public final Result get(long timeout, TimeUnit unit) throws InterruptedException,
+ ExecutionException, TimeoutException {
+ return mFuture.get(timeout, unit);
+ }
+
+ /**
+ * Executes the task with the specified parameters. The task returns
+ * itself (this) so that the caller can keep a reference to it.
+ *
+ * <p>Note: this function schedules the task on a queue for a single background
+ * thread or pool of threads depending on the platform version. When first
+ * introduced, AsyncTasks were executed serially on a single background thread.
+ * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
+ * to a pool of threads allowing multiple tasks to operate in parallel. Starting
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being
+ * executed on a single thread to avoid common application errors caused
+ * by parallel execution. If you truly want parallel execution, you can use
+ * the {@link #executeOnExecutor} version of this method
+ * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings
+ * on its use.
+ *
+ * <p>This method must be invoked on the UI thread.
+ *
+ * @param params The parameters of the task.
+ *
+ * @return This instance of AsyncTask.
+ *
+ * @throws IllegalStateException If {@link #getStatus()} returns either
+ * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
+ *
+ * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
+ * @see #execute(Runnable)
+ */
+ public final AsyncTask<Params, Progress, Result> execute(Params... params) {
+ return executeOnExecutor(sDefaultExecutor, params);
+ }
+
+ /**
+ * Executes the task with the specified parameters. The task returns
+ * itself (this) so that the caller can keep a reference to it.
+ *
+ * <p>This method is typically used with {@link #THREAD_POOL_EXECUTOR} to
+ * allow multiple tasks to run in parallel on a pool of threads managed by
+ * AsyncTask, however you can also use your own {@link java.util.concurrent.Executor} for custom
+ * behavior.
+ *
+ * <p><em>Warning:</em> Allowing multiple tasks to run in parallel from
+ * a thread pool is generally <em>not</em> what one wants, because the order
+ * of their operation is not defined. For example, if these tasks are used
+ * to modify any state in common (such as writing a file due to a button click),
+ * there are no guarantees on the order of the modifications.
+ * Without careful work it is possible in rare cases for the newer version
+ * of the data to be over-written by an older one, leading to obscure data
+ * loss and stability issues. Such changes are best
+ * executed in serial; to guarantee such work is serialized regardless of
+ * platform version you can use this function with {@link #SERIAL_EXECUTOR}.
+ *
+ * <p>This method must be invoked on the UI thread.
+ *
+ * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a
+ * convenient process-wide thread pool for tasks that are loosely coupled.
+ * @param params The parameters of the task.
+ *
+ * @return This instance of AsyncTask.
+ *
+ * @throws IllegalStateException If {@link #getStatus()} returns either
+ * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
+ *
+ * @see #execute(Object[])
+ */
+ public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
+ Params... params) {
+ if (mStatus != Status.PENDING) {
+ switch (mStatus) {
+ case RUNNING:
+ throw new IllegalStateException("Cannot execute task:"
+ + " the task is already running.");
+ case FINISHED:
+ throw new IllegalStateException("Cannot execute task:"
+ + " the task has already been executed "
+ + "(a task can be executed only once)");
+ }
+ }
+
+ mStatus = Status.RUNNING;
+
+ onPreExecute();
+
+ mWorker.mParams = params;
+ exec.execute(mFuture);
+
+ return this;
+ }
+
+ /**
+ * Convenience version of {@link #execute(Object...)} for use with
+ * a simple Runnable object. See {@link #execute(Object[])} for more
+ * information on the order of execution.
+ *
+ * @see #execute(Object[])
+ * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
+ */
+ public static void execute(Runnable runnable) {
+ sDefaultExecutor.execute(runnable);
+ }
+
+ /**
+ * This method can be invoked from {@link #doInBackground} to
+ * publish updates on the UI thread while the background computation is
+ * still running. Each call to this method will trigger the execution of
+ * {@link #onProgressUpdate} on the UI thread.
+ *
+ * {@link #onProgressUpdate} will note be called if the task has been
+ * canceled.
+ *
+ * @param values The progress values to update the UI with.
+ *
+ * @see #onProgressUpdate
+ * @see #doInBackground
+ */
+ protected final void publishProgress(Progress... values) {
+ if (!isCancelled()) {
+ sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
+ new AsyncTaskResult<Progress>(this, values)).sendToTarget();
+ }
+ }
+
+ private void finish(Result result) {
+ if (isCancelled()) {
+ onCancelled(result);
+ } else {
+ onPostExecute(result);
+ }
+ mStatus = Status.FINISHED;
+ }
+
+ private static class InternalHandler extends Handler {
+ @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
+ @Override
+ public void handleMessage(Message msg) {
+ AsyncTaskResult result = (AsyncTaskResult) msg.obj;
+ switch (msg.what) {
+ case MESSAGE_POST_RESULT:
+ // There is only one result
+ result.mTask.finish(result.mData[0]);
+ break;
+ case MESSAGE_POST_PROGRESS:
+ result.mTask.onProgressUpdate(result.mData);
+ break;
+ }
+ }
+ }
+
+ private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
+ Params[] mParams;
+ }
+
+ @SuppressWarnings({"RawUseOfParameterizedType"})
+ private static class AsyncTaskResult<Data> {
+ final AsyncTask mTask;
+ final Data[] mData;
+
+ AsyncTaskResult(AsyncTask task, Data... data) {
+ mTask = task;
+ mData = data;
+ }
+ }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/DiskLruCache.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/DiskLruCache.java
new file mode 100644
index 0000000..4c14345
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/DiskLruCache.java
@@ -0,0 +1,953 @@
+/*
+ * 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 com.example.android.displayingbitmaps.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.reflect.Array;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ ******************************************************************************
+ * Taken from the JB source code, can be found in:
+ * libcore/luni/src/main/java/libcore/io/DiskLruCache.java
+ * or direct link:
+ * https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java
+ ******************************************************************************
+ *
+ * A cache that uses a bounded amount of space on a filesystem. Each cache
+ * entry has a string key and a fixed number of values. Values are byte
+ * sequences, accessible as streams or files. Each value must be between {@code
+ * 0} and {@code Integer.MAX_VALUE} bytes in length.
+ *
+ * <p>The cache stores its data in a directory on the filesystem. This
+ * directory must be exclusive to the cache; the cache may delete or overwrite
+ * files from its directory. It is an error for multiple processes to use the
+ * same cache directory at the same time.
+ *
+ * <p>This cache limits the number of bytes that it will store on the
+ * filesystem. When the number of stored bytes exceeds the limit, the cache will
+ * remove entries in the background until the limit is satisfied. The limit is
+ * not strict: the cache may temporarily exceed it while waiting for files to be
+ * deleted. The limit does not include filesystem overhead or the cache
+ * journal so space-sensitive applications should set a conservative limit.
+ *
+ * <p>Clients call {@link #edit} to create or update the values of an entry. An
+ * entry may have only one editor at one time; if a value is not available to be
+ * edited then {@link #edit} will return null.
+ * <ul>
+ * <li>When an entry is being <strong>created</strong> it is necessary to
+ * supply a full set of values; the empty value should be used as a
+ * placeholder if necessary.
+ * <li>When an entry is being <strong>edited</strong>, it is not necessary
+ * to supply data for every value; values default to their previous
+ * value.
+ * </ul>
+ * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
+ * or {@link Editor#abort}. Committing is atomic: a read observes the full set
+ * of values as they were before or after the commit, but never a mix of values.
+ *
+ * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
+ * observe the value at the time that {@link #get} was called. Updates and
+ * removals after the call do not impact ongoing reads.
+ *
+ * <p>This class is tolerant of some I/O errors. If files are missing from the
+ * filesystem, the corresponding entries will be dropped from the cache. If
+ * an error occurs while writing a cache value, the edit will fail silently.
+ * Callers should handle other problems by catching {@code IOException} and
+ * responding appropriately.
+ */
+public final class DiskLruCache implements Closeable {
+ static final String JOURNAL_FILE = "journal";
+ static final String JOURNAL_FILE_TMP = "journal.tmp";
+ static final String MAGIC = "libcore.io.DiskLruCache";
+ static final String VERSION_1 = "1";
+ static final long ANY_SEQUENCE_NUMBER = -1;
+ private static final String CLEAN = "CLEAN";
+ private static final String DIRTY = "DIRTY";
+ private static final String REMOVE = "REMOVE";
+ private static final String READ = "READ";
+
+ private static final Charset UTF_8 = Charset.forName("UTF-8");
+ private static final int IO_BUFFER_SIZE = 8 * 1024;
+
+ /*
+ * This cache uses a journal file named "journal". A typical journal file
+ * looks like this:
+ * libcore.io.DiskLruCache
+ * 1
+ * 100
+ * 2
+ *
+ * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
+ * DIRTY 335c4c6028171cfddfbaae1a9c313c52
+ * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
+ * REMOVE 335c4c6028171cfddfbaae1a9c313c52
+ * DIRTY 1ab96a171faeeee38496d8b330771a7a
+ * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
+ * READ 335c4c6028171cfddfbaae1a9c313c52
+ * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
+ *
+ * The first five lines of the journal form its header. They are the
+ * constant string "libcore.io.DiskLruCache", the disk cache's version,
+ * the application's version, the value count, and a blank line.
+ *
+ * Each of the subsequent lines in the file is a record of the state of a
+ * cache entry. Each line contains space-separated values: a state, a key,
+ * and optional state-specific values.
+ * o DIRTY lines track that an entry is actively being created or updated.
+ * Every successful DIRTY action should be followed by a CLEAN or REMOVE
+ * action. DIRTY lines without a matching CLEAN or REMOVE indicate that
+ * temporary files may need to be deleted.
+ * o CLEAN lines track a cache entry that has been successfully published
+ * and may be read. A publish line is followed by the lengths of each of
+ * its values.
+ * o READ lines track accesses for LRU.
+ * o REMOVE lines track entries that have been deleted.
+ *
+ * The journal file is appended to as cache operations occur. The journal may
+ * occasionally be compacted by dropping redundant lines. A temporary file named
+ * "journal.tmp" will be used during compaction; that file should be deleted if
+ * it exists when the cache is opened.
+ */
+
+ private final File directory;
+ private final File journalFile;
+ private final File journalFileTmp;
+ private final int appVersion;
+ private final long maxSize;
+ private final int valueCount;
+ private long size = 0;
+ private Writer journalWriter;
+ private final LinkedHashMap<String, Entry> lruEntries
+ = new LinkedHashMap<String, Entry>(0, 0.75f, true);
+ private int redundantOpCount;
+
+ /**
+ * To differentiate between old and current snapshots, each entry is given
+ * a sequence number each time an edit is committed. A snapshot is stale if
+ * its sequence number is not equal to its entry's sequence number.
+ */
+ private long nextSequenceNumber = 0;
+
+ /* From java.util.Arrays */
+ @SuppressWarnings("unchecked")
+ private static <T> T[] copyOfRange(T[] original, int start, int end) {
+ final int originalLength = original.length; // For exception priority compatibility.
+ if (start > end) {
+ throw new IllegalArgumentException();
+ }
+ if (start < 0 || start > originalLength) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ final int resultLength = end - start;
+ final int copyLength = Math.min(resultLength, originalLength - start);
+ final T[] result = (T[]) Array
+ .newInstance(original.getClass().getComponentType(), resultLength);
+ System.arraycopy(original, start, result, 0, copyLength);
+ return result;
+ }
+
+ /**
+ * Returns the remainder of 'reader' as a string, closing it when done.
+ */
+ public static String readFully(Reader reader) throws IOException {
+ try {
+ StringWriter writer = new StringWriter();
+ char[] buffer = new char[1024];
+ int count;
+ while ((count = reader.read(buffer)) != -1) {
+ writer.write(buffer, 0, count);
+ }
+ return writer.toString();
+ } finally {
+ reader.close();
+ }
+ }
+
+ /**
+ * Returns the ASCII characters up to but not including the next "\r\n", or
+ * "\n".
+ *
+ * @throws java.io.EOFException if the stream is exhausted before the next newline
+ * character.
+ */
+ public static String readAsciiLine(InputStream in) throws IOException {
+ // TODO: support UTF-8 here instead
+
+ StringBuilder result = new StringBuilder(80);
+ while (true) {
+ int c = in.read();
+ if (c == -1) {
+ throw new EOFException();
+ } else if (c == '\n') {
+ break;
+ }
+
+ result.append((char) c);
+ }
+ int length = result.length();
+ if (length > 0 && result.charAt(length - 1) == '\r') {
+ result.setLength(length - 1);
+ }
+ return result.toString();
+ }
+
+ /**
+ * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.
+ */
+ public static void closeQuietly(Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ /**
+ * Recursively delete everything in {@code dir}.
+ */
+ // TODO: this should specify paths as Strings rather than as Files
+ public static void deleteContents(File dir) throws IOException {
+ File[] files = dir.listFiles();
+ if (files == null) {
+ throw new IllegalArgumentException("not a directory: " + dir);
+ }
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteContents(file);
+ }
+ if (!file.delete()) {
+ throw new IOException("failed to delete file: " + file);
+ }
+ }
+ }
+
+ /** This cache uses a single background thread to evict entries. */
+ private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
+ 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+ private final Callable<Void> cleanupCallable = new Callable<Void>() {
+ @Override public Void call() throws Exception {
+ synchronized (DiskLruCache.this) {
+ if (journalWriter == null) {
+ return null; // closed
+ }
+ trimToSize();
+ if (journalRebuildRequired()) {
+ rebuildJournal();
+ redundantOpCount = 0;
+ }
+ }
+ return null;
+ }
+ };
+
+ private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
+ this.directory = directory;
+ this.appVersion = appVersion;
+ this.journalFile = new File(directory, JOURNAL_FILE);
+ this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);
+ this.valueCount = valueCount;
+ this.maxSize = maxSize;
+ }
+
+ /**
+ * Opens the cache in {@code directory}, creating a cache if none exists
+ * there.
+ *
+ * @param directory a writable directory
+ * @param appVersion
+ * @param valueCount the number of values per cache entry. Must be positive.
+ * @param maxSize the maximum number of bytes this cache should use to store
+ * @throws java.io.IOException if reading or writing the cache directory fails
+ */
+ public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
+ throws IOException {
+ if (maxSize <= 0) {
+ throw new IllegalArgumentException("maxSize <= 0");
+ }
+ if (valueCount <= 0) {
+ throw new IllegalArgumentException("valueCount <= 0");
+ }
+
+ // prefer to pick up where we left off
+ DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+ if (cache.journalFile.exists()) {
+ try {
+ cache.readJournal();
+ cache.processJournal();
+ cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),
+ IO_BUFFER_SIZE);
+ return cache;
+ } catch (IOException journalIsCorrupt) {
+// System.logW("DiskLruCache " + directory + " is corrupt: "
+// + journalIsCorrupt.getMessage() + ", removing");
+ cache.delete();
+ }
+ }
+
+ // create a new empty cache
+ directory.mkdirs();
+ cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+ cache.rebuildJournal();
+ return cache;
+ }
+
+ private void readJournal() throws IOException {
+ InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE);
+ try {
+ String magic = readAsciiLine(in);
+ String version = readAsciiLine(in);
+ String appVersionString = readAsciiLine(in);
+ String valueCountString = readAsciiLine(in);
+ String blank = readAsciiLine(in);
+ if (!MAGIC.equals(magic)
+ || !VERSION_1.equals(version)
+ || !Integer.toString(appVersion).equals(appVersionString)
+ || !Integer.toString(valueCount).equals(valueCountString)
+ || !"".equals(blank)) {
+ throw new IOException("unexpected journal header: ["
+ + magic + ", " + version + ", " + valueCountString + ", " + blank + "]");
+ }
+
+ while (true) {
+ try {
+ readJournalLine(readAsciiLine(in));
+ } catch (EOFException endOfJournal) {
+ break;
+ }
+ }
+ } finally {
+ closeQuietly(in);
+ }
+ }
+
+ private void readJournalLine(String line) throws IOException {
+ String[] parts = line.split(" ");
+ if (parts.length < 2) {
+ throw new IOException("unexpected journal line: " + line);
+ }
+
+ String key = parts[1];
+ if (parts[0].equals(REMOVE) && parts.length == 2) {
+ lruEntries.remove(key);
+ return;
+ }
+
+ Entry entry = lruEntries.get(key);
+ if (entry == null) {
+ entry = new Entry(key);
+ lruEntries.put(key, entry);
+ }
+
+ if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {
+ entry.readable = true;
+ entry.currentEditor = null;
+ entry.setLengths(copyOfRange(parts, 2, parts.length));
+ } else if (parts[0].equals(DIRTY) && parts.length == 2) {
+ entry.currentEditor = new Editor(entry);
+ } else if (parts[0].equals(READ) && parts.length == 2) {
+ // this work was already done by calling lruEntries.get()
+ } else {
+ throw new IOException("unexpected journal line: " + line);
+ }
+ }
+
+ /**
+ * Computes the initial size and collects garbage as a part of opening the
+ * cache. Dirty entries are assumed to be inconsistent and will be deleted.
+ */
+ private void processJournal() throws IOException {
+ deleteIfExists(journalFileTmp);
+ for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
+ Entry entry = i.next();
+ if (entry.currentEditor == null) {
+ for (int t = 0; t < valueCount; t++) {
+ size += entry.lengths[t];
+ }
+ } else {
+ entry.currentEditor = null;
+ for (int t = 0; t < valueCount; t++) {
+ deleteIfExists(entry.getCleanFile(t));
+ deleteIfExists(entry.getDirtyFile(t));
+ }
+ i.remove();
+ }
+ }
+ }
+
+ /**
+ * Creates a new journal that omits redundant information. This replaces the
+ * current journal if it exists.
+ */
+ private synchronized void rebuildJournal() throws IOException {
+ if (journalWriter != null) {
+ journalWriter.close();
+ }
+
+ Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);
+ writer.write(MAGIC);
+ writer.write("\n");
+ writer.write(VERSION_1);
+ writer.write("\n");
+ writer.write(Integer.toString(appVersion));
+ writer.write("\n");
+ writer.write(Integer.toString(valueCount));
+ writer.write("\n");
+ writer.write("\n");
+
+ for (Entry entry : lruEntries.values()) {
+ if (entry.currentEditor != null) {
+ writer.write(DIRTY + ' ' + entry.key + '\n');
+ } else {
+ writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
+ }
+ }
+
+ writer.close();
+ journalFileTmp.renameTo(journalFile);
+ journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);
+ }
+
+ private static void deleteIfExists(File file) throws IOException {
+// try {
+// Libcore.os.remove(file.getPath());
+// } catch (ErrnoException errnoException) {
+// if (errnoException.errno != OsConstants.ENOENT) {
+// throw errnoException.rethrowAsIOException();
+// }
+// }
+ if (file.exists() && !file.delete()) {
+ throw new IOException();
+ }
+ }
+
+ /**
+ * Returns a snapshot of the entry named {@code key}, or null if it doesn't
+ * exist is not currently readable. If a value is returned, it is moved to
+ * the head of the LRU queue.
+ */
+ public synchronized Snapshot get(String key) throws IOException {
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (entry == null) {
+ return null;
+ }
+
+ if (!entry.readable) {
+ return null;
+ }
+
+ /*
+ * Open all streams eagerly to guarantee that we see a single published
+ * snapshot. If we opened streams lazily then the streams could come
+ * from different edits.
+ */
+ InputStream[] ins = new InputStream[valueCount];
+ try {
+ for (int i = 0; i < valueCount; i++) {
+ ins[i] = new FileInputStream(entry.getCleanFile(i));
+ }
+ } catch (FileNotFoundException e) {
+ // a file must have been deleted manually!
+ return null;
+ }
+
+ redundantOpCount++;
+ journalWriter.append(READ + ' ' + key + '\n');
+ if (journalRebuildRequired()) {
+ executorService.submit(cleanupCallable);
+ }
+
+ return new Snapshot(key, entry.sequenceNumber, ins);
+ }
+
+ /**
+ * Returns an editor for the entry named {@code key}, or null if another
+ * edit is in progress.
+ */
+ public Editor edit(String key) throws IOException {
+ return edit(key, ANY_SEQUENCE_NUMBER);
+ }
+
+ private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
+ && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
+ return null; // snapshot is stale
+ }
+ if (entry == null) {
+ entry = new Entry(key);
+ lruEntries.put(key, entry);
+ } else if (entry.currentEditor != null) {
+ return null; // another edit is in progress
+ }
+
+ Editor editor = new Editor(entry);
+ entry.currentEditor = editor;
+
+ // flush the journal before creating files to prevent file leaks
+ journalWriter.write(DIRTY + ' ' + key + '\n');
+ journalWriter.flush();
+ return editor;
+ }
+
+ /**
+ * Returns the directory where this cache stores its data.
+ */
+ public File getDirectory() {
+ return directory;
+ }
+
+ /**
+ * Returns the maximum number of bytes that this cache should use to store
+ * its data.
+ */
+ public long maxSize() {
+ return maxSize;
+ }
+
+ /**
+ * Returns the number of bytes currently being used to store the values in
+ * this cache. This may be greater than the max size if a background
+ * deletion is pending.
+ */
+ public synchronized long size() {
+ return size;
+ }
+
+ private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
+ Entry entry = editor.entry;
+ if (entry.currentEditor != editor) {
+ throw new IllegalStateException();
+ }
+
+ // if this edit is creating the entry for the first time, every index must have a value
+ if (success && !entry.readable) {
+ for (int i = 0; i < valueCount; i++) {
+ if (!entry.getDirtyFile(i).exists()) {
+ editor.abort();
+ throw new IllegalStateException("edit didn't create file " + i);
+ }
+ }
+ }
+
+ for (int i = 0; i < valueCount; i++) {
+ File dirty = entry.getDirtyFile(i);
+ if (success) {
+ if (dirty.exists()) {
+ File clean = entry.getCleanFile(i);
+ dirty.renameTo(clean);
+ long oldLength = entry.lengths[i];
+ long newLength = clean.length();
+ entry.lengths[i] = newLength;
+ size = size - oldLength + newLength;
+ }
+ } else {
+ deleteIfExists(dirty);
+ }
+ }
+
+ redundantOpCount++;
+ entry.currentEditor = null;
+ if (entry.readable | success) {
+ entry.readable = true;
+ journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
+ if (success) {
+ entry.sequenceNumber = nextSequenceNumber++;
+ }
+ } else {
+ lruEntries.remove(entry.key);
+ journalWriter.write(REMOVE + ' ' + entry.key + '\n');
+ }
+
+ if (size > maxSize || journalRebuildRequired()) {
+ executorService.submit(cleanupCallable);
+ }
+ }
+
+ /**
+ * We only rebuild the journal when it will halve the size of the journal
+ * and eliminate at least 2000 ops.
+ */
+ private boolean journalRebuildRequired() {
+ final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;
+ return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD
+ && redundantOpCount >= lruEntries.size();
+ }
+
+ /**
+ * Drops the entry for {@code key} if it exists and can be removed. Entries
+ * actively being edited cannot be removed.
+ *
+ * @return true if an entry was removed.
+ */
+ public synchronized boolean remove(String key) throws IOException {
+ checkNotClosed();
+ validateKey(key);
+ Entry entry = lruEntries.get(key);
+ if (entry == null || entry.currentEditor != null) {
+ return false;
+ }
+
+ for (int i = 0; i < valueCount; i++) {
+ File file = entry.getCleanFile(i);
+ if (!file.delete()) {
+ throw new IOException("failed to delete " + file);
+ }
+ size -= entry.lengths[i];
+ entry.lengths[i] = 0;
+ }
+
+ redundantOpCount++;
+ journalWriter.append(REMOVE + ' ' + key + '\n');
+ lruEntries.remove(key);
+
+ if (journalRebuildRequired()) {
+ executorService.submit(cleanupCallable);
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns true if this cache has been closed.
+ */
+ public boolean isClosed() {
+ return journalWriter == null;
+ }
+
+ private void checkNotClosed() {
+ if (journalWriter == null) {
+ throw new IllegalStateException("cache is closed");
+ }
+ }
+
+ /**
+ * Force buffered operations to the filesystem.
+ */
+ public synchronized void flush() throws IOException {
+ checkNotClosed();
+ trimToSize();
+ journalWriter.flush();
+ }
+
+ /**
+ * Closes this cache. Stored values will remain on the filesystem.
+ */
+ public synchronized void close() throws IOException {
+ if (journalWriter == null) {
+ return; // already closed
+ }
+ for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
+ if (entry.currentEditor != null) {
+ entry.currentEditor.abort();
+ }
+ }
+ trimToSize();
+ journalWriter.close();
+ journalWriter = null;
+ }
+
+ private void trimToSize() throws IOException {
+ while (size > maxSize) {
+// Map.Entry<String, Entry> toEvict = lruEntries.eldest();
+ final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
+ remove(toEvict.getKey());
+ }
+ }
+
+ /**
+ * Closes the cache and deletes all of its stored values. This will delete
+ * all files in the cache directory including files that weren't created by
+ * the cache.
+ */
+ public void delete() throws IOException {
+ close();
+ deleteContents(directory);
+ }
+
+ private void validateKey(String key) {
+ if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {
+ throw new IllegalArgumentException(
+ "keys must not contain spaces or newlines: \"" + key + "\"");
+ }
+ }
+
+ private static String inputStreamToString(InputStream in) throws IOException {
+ return readFully(new InputStreamReader(in, UTF_8));
+ }
+
+ /**
+ * A snapshot of the values for an entry.
+ */
+ public final class Snapshot implements Closeable {
+ private final String key;
+ private final long sequenceNumber;
+ private final InputStream[] ins;
+
+ private Snapshot(String key, long sequenceNumber, InputStream[] ins) {
+ this.key = key;
+ this.sequenceNumber = sequenceNumber;
+ this.ins = ins;
+ }
+
+ /**
+ * Returns an editor for this snapshot's entry, or null if either the
+ * entry has changed since this snapshot was created or if another edit
+ * is in progress.
+ */
+ public Editor edit() throws IOException {
+ return DiskLruCache.this.edit(key, sequenceNumber);
+ }
+
+ /**
+ * Returns the unbuffered stream with the value for {@code index}.
+ */
+ public InputStream getInputStream(int index) {
+ return ins[index];
+ }
+
+ /**
+ * Returns the string value for {@code index}.
+ */
+ public String getString(int index) throws IOException {
+ return inputStreamToString(getInputStream(index));
+ }
+
+ @Override public void close() {
+ for (InputStream in : ins) {
+ closeQuietly(in);
+ }
+ }
+ }
+
+ /**
+ * Edits the values for an entry.
+ */
+ public final class Editor {
+ private final Entry entry;
+ private boolean hasErrors;
+
+ private Editor(Entry entry) {
+ this.entry = entry;
+ }
+
+ /**
+ * Returns an unbuffered input stream to read the last committed value,
+ * or null if no value has been committed.
+ */
+ public InputStream newInputStream(int index) throws IOException {
+ synchronized (DiskLruCache.this) {
+ if (entry.currentEditor != this) {
+ throw new IllegalStateException();
+ }
+ if (!entry.readable) {
+ return null;
+ }
+ return new FileInputStream(entry.getCleanFile(index));
+ }
+ }
+
+ /**
+ * Returns the last committed value as a string, or null if no value
+ * has been committed.
+ */
+ public String getString(int index) throws IOException {
+ InputStream in = newInputStream(index);
+ return in != null ? inputStreamToString(in) : null;
+ }
+
+ /**
+ * Returns a new unbuffered output stream to write the value at
+ * {@code index}. If the underlying output stream encounters errors
+ * when writing to the filesystem, this edit will be aborted when
+ * {@link #commit} is called. The returned output stream does not throw
+ * IOExceptions.
+ */
+ public OutputStream newOutputStream(int index) throws IOException {
+ synchronized (DiskLruCache.this) {
+ if (entry.currentEditor != this) {
+ throw new IllegalStateException();
+ }
+ return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));
+ }
+ }
+
+ /**
+ * Sets the value at {@code index} to {@code value}.
+ */
+ public void set(int index, String value) throws IOException {
+ Writer writer = null;
+ try {
+ writer = new OutputStreamWriter(newOutputStream(index), UTF_8);
+ writer.write(value);
+ } finally {
+ closeQuietly(writer);
+ }
+ }
+
+ /**
+ * Commits this edit so it is visible to readers. This releases the
+ * edit lock so another edit may be started on the same key.
+ */
+ public void commit() throws IOException {
+ if (hasErrors) {
+ completeEdit(this, false);
+ remove(entry.key); // the previous entry is stale
+ } else {
+ completeEdit(this, true);
+ }
+ }
+
+ /**
+ * Aborts this edit. This releases the edit lock so another edit may be
+ * started on the same key.
+ */
+ public void abort() throws IOException {
+ completeEdit(this, false);
+ }
+
+ private class FaultHidingOutputStream extends FilterOutputStream {
+ private FaultHidingOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ @Override public void write(int oneByte) {
+ try {
+ out.write(oneByte);
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+
+ @Override public void write(byte[] buffer, int offset, int length) {
+ try {
+ out.write(buffer, offset, length);
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+
+ @Override public void close() {
+ try {
+ out.close();
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+
+ @Override public void flush() {
+ try {
+ out.flush();
+ } catch (IOException e) {
+ hasErrors = true;
+ }
+ }
+ }
+ }
+
+ private final class Entry {
+ private final String key;
+
+ /** Lengths of this entry's files. */
+ private final long[] lengths;
+
+ /** True if this entry has ever been published */
+ private boolean readable;
+
+ /** The ongoing edit or null if this entry is not being edited. */
+ private Editor currentEditor;
+
+ /** The sequence number of the most recently committed edit to this entry. */
+ private long sequenceNumber;
+
+ private Entry(String key) {
+ this.key = key;
+ this.lengths = new long[valueCount];
+ }
+
+ public String getLengths() throws IOException {
+ StringBuilder result = new StringBuilder();
+ for (long size : lengths) {
+ result.append(' ').append(size);
+ }
+ return result.toString();
+ }
+
+ /**
+ * Set lengths using decimal numbers like "10123".
+ */
+ private void setLengths(String[] strings) throws IOException {
+ if (strings.length != valueCount) {
+ throw invalidLengths(strings);
+ }
+
+ try {
+ for (int i = 0; i < strings.length; i++) {
+ lengths[i] = Long.parseLong(strings[i]);
+ }
+ } catch (NumberFormatException e) {
+ throw invalidLengths(strings);
+ }
+ }
+
+ private IOException invalidLengths(String[] strings) throws IOException {
+ throw new IOException("unexpected journal line: " + Arrays.toString(strings));
+ }
+
+ public File getCleanFile(int i) {
+ return new File(directory, key + "." + i);
+ }
+
+ public File getDirtyFile(int i) {
+ return new File(directory, key + "." + i + ".tmp");
+ }
+ }
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/ImageCache.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/ImageCache.java
new file mode 100644
index 0000000..41da56c
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/ImageCache.java
@@ -0,0 +1,738 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.displayingbitmaps.util;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.StatFs;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.util.LruCache;
+
+import com.example.android.common.logger.Log;
+import com.example.android.displayingbitmaps.BuildConfig;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.SoftReference;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * This class handles disk and memory caching of bitmaps in conjunction with the
+ * {@link ImageWorker} class and its subclasses. Use
+ * {@link ImageCache#getInstance(android.support.v4.app.FragmentManager, ImageCacheParams)} to get an instance of this
+ * class, although usually a cache should be added directly to an {@link ImageWorker} by calling
+ * {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCacheParams)}.
+ */
+public class ImageCache {
+ private static final String TAG = "ImageCache";
+
+ // Default memory cache size in kilobytes
+ private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 5; // 5MB
+
+ // Default disk cache size in bytes
+ private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
+
+ // Compression settings when writing images to disk cache
+ private static final CompressFormat DEFAULT_COMPRESS_FORMAT = CompressFormat.JPEG;
+ private static final int DEFAULT_COMPRESS_QUALITY = 70;
+ private static final int DISK_CACHE_INDEX = 0;
+
+ // Constants to easily toggle various caches
+ private static final boolean DEFAULT_MEM_CACHE_ENABLED = true;
+ private static final boolean DEFAULT_DISK_CACHE_ENABLED = true;
+ private static final boolean DEFAULT_INIT_DISK_CACHE_ON_CREATE = false;
+
+ private DiskLruCache mDiskLruCache;
+ private LruCache<String, BitmapDrawable> mMemoryCache;
+ private ImageCacheParams mCacheParams;
+ private final Object mDiskCacheLock = new Object();
+ private boolean mDiskCacheStarting = true;
+
+ private Set<SoftReference<Bitmap>> mReusableBitmaps;
+
+ /**
+ * Create a new ImageCache object using the specified parameters. This should not be
+ * called directly by other classes, instead use
+ * {@link ImageCache#getInstance(android.support.v4.app.FragmentManager, ImageCacheParams)} to fetch an ImageCache
+ * instance.
+ *
+ * @param cacheParams The cache parameters to use to initialize the cache
+ */
+ private ImageCache(ImageCacheParams cacheParams) {
+ init(cacheParams);
+ }
+
+ /**
+ * Return an {@link ImageCache} instance. A {@link RetainFragment} is used to retain the
+ * ImageCache object across configuration changes such as a change in device orientation.
+ *
+ * @param fragmentManager The fragment manager to use when dealing with the retained fragment.
+ * @param cacheParams The cache parameters to use if the ImageCache needs instantiation.
+ * @return An existing retained ImageCache object or a new one if one did not exist
+ */
+ public static ImageCache getInstance(
+ FragmentManager fragmentManager, ImageCacheParams cacheParams) {
+
+ // Search for, or create an instance of the non-UI RetainFragment
+ final RetainFragment mRetainFragment = findOrCreateRetainFragment(fragmentManager);
+
+ // See if we already have an ImageCache stored in RetainFragment
+ ImageCache imageCache = (ImageCache) mRetainFragment.getObject();
+
+ // No existing ImageCache, create one and store it in RetainFragment
+ if (imageCache == null) {
+ imageCache = new ImageCache(cacheParams);
+ mRetainFragment.setObject(imageCache);
+ }
+
+ return imageCache;
+ }
+
+ /**
+ * Initialize the cache, providing all parameters.
+ *
+ * @param cacheParams The cache parameters to initialize the cache
+ */
+ private void init(ImageCacheParams cacheParams) {
+ mCacheParams = cacheParams;
+
+ //BEGIN_INCLUDE(init_memory_cache)
+ // Set up memory cache
+ if (mCacheParams.memoryCacheEnabled) {
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "Memory cache created (size = " + mCacheParams.memCacheSize + ")");
+ }
+
+ // If we're running on Honeycomb or newer, create a set of reusable bitmaps that can be
+ // populated into the inBitmap field of BitmapFactory.Options. Note that the set is
+ // of SoftReferences which will actually not be very effective due to the garbage
+ // collector being aggressive clearing Soft/WeakReferences. A better approach
+ // would be to use a strongly references bitmaps, however this would require some
+ // balancing of memory usage between this set and the bitmap LruCache. It would also
+ // require knowledge of the expected size of the bitmaps. From Honeycomb to JellyBean
+ // the size would need to be precise, from KitKat onward the size would just need to
+ // be the upper bound (due to changes in how inBitmap can re-use bitmaps).
+ if (Utils.hasHoneycomb()) {
+ mReusableBitmaps =
+ Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
+ }
+
+ mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
+
+ /**
+ * Notify the removed entry that is no longer being cached
+ */
+ @Override
+ protected void entryRemoved(boolean evicted, String key,
+ BitmapDrawable oldValue, BitmapDrawable newValue) {
+ if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
+ // The removed entry is a recycling drawable, so notify it
+ // that it has been removed from the memory cache
+ ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
+ } else {
+ // The removed entry is a standard BitmapDrawable
+
+ if (Utils.hasHoneycomb()) {
+ // We're running on Honeycomb or later, so add the bitmap
+ // to a SoftReference set for possible use with inBitmap later
+ mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));
+ }
+ }
+ }
+
+ /**
+ * Measure item size in kilobytes rather than units which is more practical
+ * for a bitmap cache
+ */
+ @Override
+ protected int sizeOf(String key, BitmapDrawable value) {
+ final int bitmapSize = getBitmapSize(value) / 1024;
+ return bitmapSize == 0 ? 1 : bitmapSize;
+ }
+ };
+ }
+ //END_INCLUDE(init_memory_cache)
+
+ // By default the disk cache is not initialized here as it should be initialized
+ // on a separate thread due to disk access.
+ if (cacheParams.initDiskCacheOnCreate) {
+ // Set up disk cache
+ initDiskCache();
+ }
+ }
+
+ /**
+ * Initializes the disk cache. Note that this includes disk access so this should not be
+ * executed on the main/UI thread. By default an ImageCache does not initialize the disk
+ * cache when it is created, instead you should call initDiskCache() to initialize it on a
+ * background thread.
+ */
+ public void initDiskCache() {
+ // Set up disk cache
+ synchronized (mDiskCacheLock) {
+ if (mDiskLruCache == null || mDiskLruCache.isClosed()) {
+ File diskCacheDir = mCacheParams.diskCacheDir;
+ if (mCacheParams.diskCacheEnabled && diskCacheDir != null) {
+ if (!diskCacheDir.exists()) {
+ diskCacheDir.mkdirs();
+ }
+ if (getUsableSpace(diskCacheDir) > mCacheParams.diskCacheSize) {
+ try {
+ mDiskLruCache = DiskLruCache.open(
+ diskCacheDir, 1, 1, mCacheParams.diskCacheSize);
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "Disk cache initialized");
+ }
+ } catch (final IOException e) {
+ mCacheParams.diskCacheDir = null;
+ Log.e(TAG, "initDiskCache - " + e);
+ }
+ }
+ }
+ }
+ mDiskCacheStarting = false;
+ mDiskCacheLock.notifyAll();
+ }
+ }
+
+ /**
+ * Adds a bitmap to both memory and disk cache.
+ * @param data Unique identifier for the bitmap to store
+ * @param value The bitmap drawable to store
+ */
+ public void addBitmapToCache(String data, BitmapDrawable value) {
+ //BEGIN_INCLUDE(add_bitmap_to_cache)
+ if (data == null || value == null) {
+ return;
+ }
+
+ // Add to memory cache
+ if (mMemoryCache != null) {
+ if (RecyclingBitmapDrawable.class.isInstance(value)) {
+ // The removed entry is a recycling drawable, so notify it
+ // that it has been added into the memory cache
+ ((RecyclingBitmapDrawable) value).setIsCached(true);
+ }
+ mMemoryCache.put(data, value);
+ }
+
+ synchronized (mDiskCacheLock) {
+ // Add to disk cache
+ if (mDiskLruCache != null) {
+ final String key = hashKeyForDisk(data);
+ OutputStream out = null;
+ try {
+ DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
+ if (snapshot == null) {
+ final DiskLruCache.Editor editor = mDiskLruCache.edit(key);
+ if (editor != null) {
+ out = editor.newOutputStream(DISK_CACHE_INDEX);
+ value.getBitmap().compress(
+ mCacheParams.compressFormat, mCacheParams.compressQuality, out);
+ editor.commit();
+ out.close();
+ }
+ } else {
+ snapshot.getInputStream(DISK_CACHE_INDEX).close();
+ }
+ } catch (final IOException e) {
+ Log.e(TAG, "addBitmapToCache - " + e);
+ } catch (Exception e) {
+ Log.e(TAG, "addBitmapToCache - " + e);
+ } finally {
+ try {
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException e) {}
+ }
+ }
+ }
+ //END_INCLUDE(add_bitmap_to_cache)
+ }
+
+ /**
+ * Get from memory cache.
+ *
+ * @param data Unique identifier for which item to get
+ * @return The bitmap drawable if found in cache, null otherwise
+ */
+ public BitmapDrawable getBitmapFromMemCache(String data) {
+ //BEGIN_INCLUDE(get_bitmap_from_mem_cache)
+ BitmapDrawable memValue = null;
+
+ if (mMemoryCache != null) {
+ memValue = mMemoryCache.get(data);
+ }
+
+ if (BuildConfig.DEBUG && memValue != null) {
+ Log.d(TAG, "Memory cache hit");
+ }
+
+ return memValue;
+ //END_INCLUDE(get_bitmap_from_mem_cache)
+ }
+
+ /**
+ * Get from disk cache.
+ *
+ * @param data Unique identifier for which item to get
+ * @return The bitmap if found in cache, null otherwise
+ */
+ public Bitmap getBitmapFromDiskCache(String data) {
+ //BEGIN_INCLUDE(get_bitmap_from_disk_cache)
+ final String key = hashKeyForDisk(data);
+ Bitmap bitmap = null;
+
+ synchronized (mDiskCacheLock) {
+ while (mDiskCacheStarting) {
+ try {
+ mDiskCacheLock.wait();
+ } catch (InterruptedException e) {}
+ }
+ if (mDiskLruCache != null) {
+ InputStream inputStream = null;
+ try {
+ final DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
+ if (snapshot != null) {
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "Disk cache hit");
+ }
+ inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
+ if (inputStream != null) {
+ FileDescriptor fd = ((FileInputStream) inputStream).getFD();
+
+ // Decode bitmap, but we don't want to sample so give
+ // MAX_VALUE as the target dimensions
+ bitmap = ImageResizer.decodeSampledBitmapFromDescriptor(
+ fd, Integer.MAX_VALUE, Integer.MAX_VALUE, this);
+ }
+ }
+ } catch (final IOException e) {
+ Log.e(TAG, "getBitmapFromDiskCache - " + e);
+ } finally {
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } catch (IOException e) {}
+ }
+ }
+ return bitmap;
+ }
+ //END_INCLUDE(get_bitmap_from_disk_cache)
+ }
+
+ /**
+ * @param options - BitmapFactory.Options with out* options populated
+ * @return Bitmap that case be used for inBitmap
+ */
+ protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
+ //BEGIN_INCLUDE(get_bitmap_from_reusable_set)
+ Bitmap bitmap = null;
+
+ if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
+ synchronized (mReusableBitmaps) {
+ final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator();
+ Bitmap item;
+
+ while (iterator.hasNext()) {
+ item = iterator.next().get();
+
+ if (null != item && item.isMutable()) {
+ // Check to see it the item can be used for inBitmap
+ if (canUseForInBitmap(item, options)) {
+ bitmap = item;
+
+ // Remove from reusable set so it can't be used again
+ iterator.remove();
+ break;
+ }
+ } else {
+ // Remove from the set if the reference has been cleared.
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ return bitmap;
+ //END_INCLUDE(get_bitmap_from_reusable_set)
+ }
+
+ /**
+ * Clears both the memory and disk cache associated with this ImageCache object. Note that
+ * this includes disk access so this should not be executed on the main/UI thread.
+ */
+ public void clearCache() {
+ if (mMemoryCache != null) {
+ mMemoryCache.evictAll();
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "Memory cache cleared");
+ }
+ }
+
+ synchronized (mDiskCacheLock) {
+ mDiskCacheStarting = true;
+ if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {
+ try {
+ mDiskLruCache.delete();
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "Disk cache cleared");
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "clearCache - " + e);
+ }
+ mDiskLruCache = null;
+ initDiskCache();
+ }
+ }
+ }
+
+ /**
+ * Flushes the disk cache associated with this ImageCache object. Note that this includes
+ * disk access so this should not be executed on the main/UI thread.
+ */
+ public void flush() {
+ synchronized (mDiskCacheLock) {
+ if (mDiskLruCache != null) {
+ try {
+ mDiskLruCache.flush();
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "Disk cache flushed");
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "flush - " + e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Closes the disk cache associated with this ImageCache object. Note that this includes
+ * disk access so this should not be executed on the main/UI thread.
+ */
+ public void close() {
+ synchronized (mDiskCacheLock) {
+ if (mDiskLruCache != null) {
+ try {
+ if (!mDiskLruCache.isClosed()) {
+ mDiskLruCache.close();
+ mDiskLruCache = null;
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "Disk cache closed");
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "close - " + e);
+ }
+ }
+ }
+ }
+
+ /**
+ * A holder class that contains cache parameters.
+ */
+ public static class ImageCacheParams {
+ public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;
+ public int diskCacheSize = DEFAULT_DISK_CACHE_SIZE;
+ public File diskCacheDir;
+ public CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
+ public int compressQuality = DEFAULT_COMPRESS_QUALITY;
+ public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;
+ public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED;
+ public boolean initDiskCacheOnCreate = DEFAULT_INIT_DISK_CACHE_ON_CREATE;
+
+ /**
+ * Create a set of image cache parameters that can be provided to
+ * {@link ImageCache#getInstance(android.support.v4.app.FragmentManager, ImageCacheParams)} or
+ * {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCacheParams)}.
+ * @param context A context to use.
+ * @param diskCacheDirectoryName A unique subdirectory name that will be appended to the
+ * application cache directory. Usually "cache" or "images"
+ * is sufficient.
+ */
+ public ImageCacheParams(Context context, String diskCacheDirectoryName) {
+ diskCacheDir = getDiskCacheDir(context, diskCacheDirectoryName);
+ }
+
+ /**
+ * Sets the memory cache size based on a percentage of the max available VM memory.
+ * Eg. setting percent to 0.2 would set the memory cache to one fifth of the available
+ * memory. Throws {@link IllegalArgumentException} if percent is < 0.01 or > .8.
+ * memCacheSize is stored in kilobytes instead of bytes as this will eventually be passed
+ * to construct a LruCache which takes an int in its constructor.
+ *
+ * This value should be chosen carefully based on a number of factors
+ * Refer to the corresponding Android Training class for more discussion:
+ * http://developer.android.com/training/displaying-bitmaps/
+ *
+ * @param percent Percent of available app memory to use to size memory cache
+ */
+ public void setMemCacheSizePercent(float percent) {
+ if (percent < 0.01f || percent > 0.8f) {
+ throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
+ + "between 0.01 and 0.8 (inclusive)");
+ }
+ memCacheSize = Math.round(percent * Runtime.getRuntime().maxMemory() / 1024);
+ }
+ }
+
+ /**
+ * @param candidate - Bitmap to check
+ * @param targetOptions - Options that have the out* value populated
+ * @return true if <code>candidate</code> can be used for inBitmap re-use with
+ * <code>targetOptions</code>
+ */
+ @TargetApi(VERSION_CODES.KITKAT)
+ private static boolean canUseForInBitmap(
+ Bitmap candidate, BitmapFactory.Options targetOptions) {
+ //BEGIN_INCLUDE(can_use_for_inbitmap)
+ if (!Utils.hasKitKat()) {
+ // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
+ return candidate.getWidth() == targetOptions.outWidth
+ && candidate.getHeight() == targetOptions.outHeight
+ && targetOptions.inSampleSize == 1;
+ }
+
+ // From Android 4.4 (KitKat) onward we can re-use if the byte size of the new bitmap
+ // is smaller than the reusable bitmap candidate allocation byte count.
+ int width = targetOptions.outWidth / targetOptions.inSampleSize;
+ int height = targetOptions.outHeight / targetOptions.inSampleSize;
+ int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
+ return byteCount <= candidate.getAllocationByteCount();
+ //END_INCLUDE(can_use_for_inbitmap)
+ }
+
+ /**
+ * Return the byte usage per pixel of a bitmap based on its configuration.
+ * @param config The bitmap configuration.
+ * @return The byte usage per pixel.
+ */
+ private static int getBytesPerPixel(Config config) {
+ if (config == Config.ARGB_8888) {
+ return 4;
+ } else if (config == Config.RGB_565) {
+ return 2;
+ } else if (config == Config.ARGB_4444) {
+ return 2;
+ } else if (config == Config.ALPHA_8) {
+ return 1;
+ }
+ return 1;
+ }
+
+ /**
+ * Get a usable cache directory (external if available, internal otherwise).
+ *
+ * @param context The context to use
+ * @param uniqueName A unique directory name to append to the cache dir
+ * @return The cache dir
+ */
+ public static File getDiskCacheDir(Context context, String uniqueName) {
+ // Check if media is mounted or storage is built-in, if so, try and use external cache dir
+ // otherwise use internal cache dir
+ final String cachePath =
+ Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
+ !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
+ context.getCacheDir().getPath();
+
+ return new File(cachePath + File.separator + uniqueName);
+ }
+
+ /**
+ * A hashing method that changes a string (like a URL) into a hash suitable for using as a
+ * disk filename.
+ */
+ public static String hashKeyForDisk(String key) {
+ String cacheKey;
+ try {
+ final MessageDigest mDigest = MessageDigest.getInstance("MD5");
+ mDigest.update(key.getBytes());
+ cacheKey = bytesToHexString(mDigest.digest());
+ } catch (NoSuchAlgorithmException e) {
+ cacheKey = String.valueOf(key.hashCode());
+ }
+ return cacheKey;
+ }
+
+ private static String bytesToHexString(byte[] bytes) {
+ // http://stackoverflow.com/questions/332079
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < bytes.length; i++) {
+ String hex = Integer.toHexString(0xFF & bytes[i]);
+ if (hex.length() == 1) {
+ sb.append('0');
+ }
+ sb.append(hex);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Get the size in bytes of a bitmap in a BitmapDrawable. Note that from Android 4.4 (KitKat)
+ * onward this returns the allocated memory size of the bitmap which can be larger than the
+ * actual bitmap data byte count (in the case it was re-used).
+ *
+ * @param value
+ * @return size in bytes
+ */
+ @TargetApi(VERSION_CODES.KITKAT)
+ public static int getBitmapSize(BitmapDrawable value) {
+ Bitmap bitmap = value.getBitmap();
+
+ // From KitKat onward use getAllocationByteCount() as allocated bytes can potentially be
+ // larger than bitmap byte count.
+ if (Utils.hasKitKat()) {
+ return bitmap.getAllocationByteCount();
+ }
+
+ if (Utils.hasHoneycombMR1()) {
+ return bitmap.getByteCount();
+ }
+
+ // Pre HC-MR1
+ return bitmap.getRowBytes() * bitmap.getHeight();
+ }
+
+ /**
+ * Check if external storage is built-in or removable.
+ *
+ * @return True if external storage is removable (like an SD card), false
+ * otherwise.
+ */
+ @TargetApi(VERSION_CODES.GINGERBREAD)
+ public static boolean isExternalStorageRemovable() {
+ if (Utils.hasGingerbread()) {
+ return Environment.isExternalStorageRemovable();
+ }
+ return true;
+ }
+
+ /**
+ * Get the external app cache directory.
+ *
+ * @param context The context to use
+ * @return The external cache dir
+ */
+ @TargetApi(VERSION_CODES.FROYO)
+ public static File getExternalCacheDir(Context context) {
+ if (Utils.hasFroyo()) {
+ return context.getExternalCacheDir();
+ }
+
+ // Before Froyo we need to construct the external cache dir ourselves
+ final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/";
+ return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir);
+ }
+
+ /**
+ * Check how much usable space is available at a given path.
+ *
+ * @param path The path to check
+ * @return The space available in bytes
+ */
+ @TargetApi(VERSION_CODES.GINGERBREAD)
+ public static long getUsableSpace(File path) {
+ if (Utils.hasGingerbread()) {
+ return path.getUsableSpace();
+ }
+ final StatFs stats = new StatFs(path.getPath());
+ return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
+ }
+
+ /**
+ * Locate an existing instance of this Fragment or if not found, create and
+ * add it using FragmentManager.
+ *
+ * @param fm The FragmentManager manager to use.
+ * @return The existing instance of the Fragment or the new instance if just
+ * created.
+ */
+ private static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
+ //BEGIN_INCLUDE(find_create_retain_fragment)
+ // Check to see if we have retained the worker fragment.
+ RetainFragment mRetainFragment = (RetainFragment) fm.findFragmentByTag(TAG);
+
+ // If not retained (or first time running), we need to create and add it.
+ if (mRetainFragment == null) {
+ mRetainFragment = new RetainFragment();
+ fm.beginTransaction().add(mRetainFragment, TAG).commitAllowingStateLoss();
+ }
+
+ return mRetainFragment;
+ //END_INCLUDE(find_create_retain_fragment)
+ }
+
+ /**
+ * A simple non-UI Fragment that stores a single Object and is retained over configuration
+ * changes. It will be used to retain the ImageCache object.
+ */
+ public static class RetainFragment extends Fragment {
+ private Object mObject;
+
+ /**
+ * Empty constructor as per the Fragment documentation
+ */
+ public RetainFragment() {}
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Make sure this Fragment is retained over a configuration change
+ setRetainInstance(true);
+ }
+
+ /**
+ * Store a single object in this Fragment.
+ *
+ * @param object The object to store
+ */
+ public void setObject(Object object) {
+ mObject = object;
+ }
+
+ /**
+ * Get the stored object.
+ *
+ * @return The stored object
+ */
+ public Object getObject() {
+ return mObject;
+ }
+ }
+
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/ImageFetcher.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/ImageFetcher.java
new file mode 100644
index 0000000..b9ccb68
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/ImageFetcher.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.displayingbitmaps.util;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Build;
+import android.widget.Toast;
+
+import com.example.android.common.logger.Log;
+import com.example.android.displayingbitmaps.BuildConfig;
+import com.example.android.displayingbitmaps.R;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * A simple subclass of {@link ImageResizer} that fetches and resizes images fetched from a URL.
+ */
+public class ImageFetcher extends ImageResizer {
+ private static final String TAG = "ImageFetcher";
+ private static final int HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB
+ private static final String HTTP_CACHE_DIR = "http";
+ private static final int IO_BUFFER_SIZE = 8 * 1024;
+
+ private DiskLruCache mHttpDiskCache;
+ private File mHttpCacheDir;
+ private boolean mHttpDiskCacheStarting = true;
+ private final Object mHttpDiskCacheLock = new Object();
+ private static final int DISK_CACHE_INDEX = 0;
+
+ /**
+ * Initialize providing a target image width and height for the processing images.
+ *
+ * @param context
+ * @param imageWidth
+ * @param imageHeight
+ */
+ public ImageFetcher(Context context, int imageWidth, int imageHeight) {
+ super(context, imageWidth, imageHeight);
+ init(context);
+ }
+
+ /**
+ * Initialize providing a single target image size (used for both width and height);
+ *
+ * @param context
+ * @param imageSize
+ */
+ public ImageFetcher(Context context, int imageSize) {
+ super(context, imageSize);
+ init(context);
+ }
+
+ private void init(Context context) {
+ checkConnection(context);
+ mHttpCacheDir = ImageCache.getDiskCacheDir(context, HTTP_CACHE_DIR);
+ }
+
+ @Override
+ protected void initDiskCacheInternal() {
+ super.initDiskCacheInternal();
+ initHttpDiskCache();
+ }
+
+ private void initHttpDiskCache() {
+ if (!mHttpCacheDir.exists()) {
+ mHttpCacheDir.mkdirs();
+ }
+ synchronized (mHttpDiskCacheLock) {
+ if (ImageCache.getUsableSpace(mHttpCacheDir) > HTTP_CACHE_SIZE) {
+ try {
+ mHttpDiskCache = DiskLruCache.open(mHttpCacheDir, 1, 1, HTTP_CACHE_SIZE);
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "HTTP cache initialized");
+ }
+ } catch (IOException e) {
+ mHttpDiskCache = null;
+ }
+ }
+ mHttpDiskCacheStarting = false;
+ mHttpDiskCacheLock.notifyAll();
+ }
+ }
+
+ @Override
+ protected void clearCacheInternal() {
+ super.clearCacheInternal();
+ synchronized (mHttpDiskCacheLock) {
+ if (mHttpDiskCache != null && !mHttpDiskCache.isClosed()) {
+ try {
+ mHttpDiskCache.delete();
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "HTTP cache cleared");
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "clearCacheInternal - " + e);
+ }
+ mHttpDiskCache = null;
+ mHttpDiskCacheStarting = true;
+ initHttpDiskCache();
+ }
+ }
+ }
+
+ @Override
+ protected void flushCacheInternal() {
+ super.flushCacheInternal();
+ synchronized (mHttpDiskCacheLock) {
+ if (mHttpDiskCache != null) {
+ try {
+ mHttpDiskCache.flush();
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "HTTP cache flushed");
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "flush - " + e);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void closeCacheInternal() {
+ super.closeCacheInternal();
+ synchronized (mHttpDiskCacheLock) {
+ if (mHttpDiskCache != null) {
+ try {
+ if (!mHttpDiskCache.isClosed()) {
+ mHttpDiskCache.close();
+ mHttpDiskCache = null;
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "HTTP cache closed");
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "closeCacheInternal - " + e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Simple network connection check.
+ *
+ * @param context
+ */
+ private void checkConnection(Context context) {
+ final ConnectivityManager cm =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
+ if (networkInfo == null || !networkInfo.isConnectedOrConnecting()) {
+ Toast.makeText(context, R.string.no_network_connection_toast, Toast.LENGTH_LONG).show();
+ Log.e(TAG, "checkConnection - no connection found");
+ }
+ }
+
+ /**
+ * The main process method, which will be called by the ImageWorker in the AsyncTask background
+ * thread.
+ *
+ * @param data The data to load the bitmap, in this case, a regular http URL
+ * @return The downloaded and resized bitmap
+ */
+ private Bitmap processBitmap(String data) {
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "processBitmap - " + data);
+ }
+
+ final String key = ImageCache.hashKeyForDisk(data);
+ FileDescriptor fileDescriptor = null;
+ FileInputStream fileInputStream = null;
+ DiskLruCache.Snapshot snapshot;
+ synchronized (mHttpDiskCacheLock) {
+ // Wait for disk cache to initialize
+ while (mHttpDiskCacheStarting) {
+ try {
+ mHttpDiskCacheLock.wait();
+ } catch (InterruptedException e) {}
+ }
+
+ if (mHttpDiskCache != null) {
+ try {
+ snapshot = mHttpDiskCache.get(key);
+ if (snapshot == null) {
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "processBitmap, not found in http cache, downloading...");
+ }
+ DiskLruCache.Editor editor = mHttpDiskCache.edit(key);
+ if (editor != null) {
+ if (downloadUrlToStream(data,
+ editor.newOutputStream(DISK_CACHE_INDEX))) {
+ editor.commit();
+ } else {
+ editor.abort();
+ }
+ }
+ snapshot = mHttpDiskCache.get(key);
+ }
+ if (snapshot != null) {
+ fileInputStream =
+ (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
+ fileDescriptor = fileInputStream.getFD();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "processBitmap - " + e);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "processBitmap - " + e);
+ } finally {
+ if (fileDescriptor == null && fileInputStream != null) {
+ try {
+ fileInputStream.close();
+ } catch (IOException e) {}
+ }
+ }
+ }
+ }
+
+ Bitmap bitmap = null;
+ if (fileDescriptor != null) {
+ bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth,
+ mImageHeight, getImageCache());
+ }
+ if (fileInputStream != null) {
+ try {
+ fileInputStream.close();
+ } catch (IOException e) {}
+ }
+ return bitmap;
+ }
+
+ @Override
+ protected Bitmap processBitmap(Object data) {
+ return processBitmap(String.valueOf(data));
+ }
+
+ /**
+ * Download a bitmap from a URL and write the content to an output stream.
+ *
+ * @param urlString The URL to fetch
+ * @return true if successful, false otherwise
+ */
+ public boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
+ disableConnectionReuseIfNecessary();
+ HttpURLConnection urlConnection = null;
+ BufferedOutputStream out = null;
+ BufferedInputStream in = null;
+
+ try {
+ final URL url = new URL(urlString);
+ urlConnection = (HttpURLConnection) url.openConnection();
+ in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
+ out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
+
+ int b;
+ while ((b = in.read()) != -1) {
+ out.write(b);
+ }
+ return true;
+ } catch (final IOException e) {
+ Log.e(TAG, "Error in downloadBitmap - " + e);
+ } finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ try {
+ if (out != null) {
+ out.close();
+ }
+ if (in != null) {
+ in.close();
+ }
+ } catch (final IOException e) {}
+ }
+ return false;
+ }
+
+ /**
+ * Workaround for bug pre-Froyo, see here for more info:
+ * http://android-developers.blogspot.com/2011/09/androids-http-clients.html
+ */
+ public static void disableConnectionReuseIfNecessary() {
+ // HTTP connection reuse which was buggy pre-froyo
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
+ System.setProperty("http.keepAlive", "false");
+ }
+ }
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/ImageResizer.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/ImageResizer.java
new file mode 100644
index 0000000..be91096
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/ImageResizer.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.displayingbitmaps.util;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Build;
+
+import com.example.android.common.logger.Log;
+import com.example.android.displayingbitmaps.BuildConfig;
+
+import java.io.FileDescriptor;
+
+/**
+ * A simple subclass of {@link ImageWorker} that resizes images from resources given a target width
+ * and height. Useful for when the input images might be too large to simply load directly into
+ * memory.
+ */
+public class ImageResizer extends ImageWorker {
+ private static final String TAG = "ImageResizer";
+ protected int mImageWidth;
+ protected int mImageHeight;
+
+ /**
+ * Initialize providing a single target image size (used for both width and height);
+ *
+ * @param context
+ * @param imageWidth
+ * @param imageHeight
+ */
+ public ImageResizer(Context context, int imageWidth, int imageHeight) {
+ super(context);
+ setImageSize(imageWidth, imageHeight);
+ }
+
+ /**
+ * Initialize providing a single target image size (used for both width and height);
+ *
+ * @param context
+ * @param imageSize
+ */
+ public ImageResizer(Context context, int imageSize) {
+ super(context);
+ setImageSize(imageSize);
+ }
+
+ /**
+ * Set the target image width and height.
+ *
+ * @param width
+ * @param height
+ */
+ public void setImageSize(int width, int height) {
+ mImageWidth = width;
+ mImageHeight = height;
+ }
+
+ /**
+ * Set the target image size (width and height will be the same).
+ *
+ * @param size
+ */
+ public void setImageSize(int size) {
+ setImageSize(size, size);
+ }
+
+ /**
+ * The main processing method. This happens in a background task. In this case we are just
+ * sampling down the bitmap and returning it from a resource.
+ *
+ * @param resId
+ * @return
+ */
+ private Bitmap processBitmap(int resId) {
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "processBitmap - " + resId);
+ }
+ return decodeSampledBitmapFromResource(mResources, resId, mImageWidth,
+ mImageHeight, getImageCache());
+ }
+
+ @Override
+ protected Bitmap processBitmap(Object data) {
+ return processBitmap(Integer.parseInt(String.valueOf(data)));
+ }
+
+ /**
+ * Decode and sample down a bitmap from resources to the requested width and height.
+ *
+ * @param res The resources object containing the image data
+ * @param resId The resource id of the image data
+ * @param reqWidth The requested width of the resulting bitmap
+ * @param reqHeight The requested height of the resulting bitmap
+ * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
+ * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
+ * that are equal to or greater than the requested width and height
+ */
+ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
+ int reqWidth, int reqHeight, ImageCache cache) {
+
+ // BEGIN_INCLUDE (read_bitmap_dimensions)
+ // First decode with inJustDecodeBounds=true to check dimensions
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeResource(res, resId, options);
+
+ // Calculate inSampleSize
+ options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+ // END_INCLUDE (read_bitmap_dimensions)
+
+ // If we're running on Honeycomb or newer, try to use inBitmap
+ if (Utils.hasHoneycomb()) {
+ addInBitmapOptions(options, cache);
+ }
+
+ // Decode bitmap with inSampleSize set
+ options.inJustDecodeBounds = false;
+ return BitmapFactory.decodeResource(res, resId, options);
+ }
+
+ /**
+ * Decode and sample down a bitmap from a file to the requested width and height.
+ *
+ * @param filename The full path of the file to decode
+ * @param reqWidth The requested width of the resulting bitmap
+ * @param reqHeight The requested height of the resulting bitmap
+ * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
+ * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
+ * that are equal to or greater than the requested width and height
+ */
+ public static Bitmap decodeSampledBitmapFromFile(String filename,
+ int reqWidth, int reqHeight, ImageCache cache) {
+
+ // First decode with inJustDecodeBounds=true to check dimensions
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(filename, options);
+
+ // Calculate inSampleSize
+ options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+
+ // If we're running on Honeycomb or newer, try to use inBitmap
+ if (Utils.hasHoneycomb()) {
+ addInBitmapOptions(options, cache);
+ }
+
+ // Decode bitmap with inSampleSize set
+ options.inJustDecodeBounds = false;
+ return BitmapFactory.decodeFile(filename, options);
+ }
+
+ /**
+ * Decode and sample down a bitmap from a file input stream to the requested width and height.
+ *
+ * @param fileDescriptor The file descriptor to read from
+ * @param reqWidth The requested width of the resulting bitmap
+ * @param reqHeight The requested height of the resulting bitmap
+ * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
+ * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
+ * that are equal to or greater than the requested width and height
+ */
+ public static Bitmap decodeSampledBitmapFromDescriptor(
+ FileDescriptor fileDescriptor, int reqWidth, int reqHeight, ImageCache cache) {
+
+ // First decode with inJustDecodeBounds=true to check dimensions
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
+
+ // Calculate inSampleSize
+ options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+
+ // Decode bitmap with inSampleSize set
+ options.inJustDecodeBounds = false;
+
+ // If we're running on Honeycomb or newer, try to use inBitmap
+ if (Utils.hasHoneycomb()) {
+ addInBitmapOptions(options, cache);
+ }
+
+ return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {
+ //BEGIN_INCLUDE(add_bitmap_options)
+ // inBitmap only works with mutable bitmaps so force the decoder to
+ // return mutable bitmaps.
+ options.inMutable = true;
+
+ if (cache != null) {
+ // Try and find a bitmap to use for inBitmap
+ Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
+
+ if (inBitmap != null) {
+ options.inBitmap = inBitmap;
+ }
+ }
+ //END_INCLUDE(add_bitmap_options)
+ }
+
+ /**
+ * Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options} object when decoding
+ * bitmaps using the decode* methods from {@link android.graphics.BitmapFactory}. This implementation calculates
+ * the closest inSampleSize that is a power of 2 and will result in the final decoded bitmap
+ * having a width and height equal to or larger than the requested width and height.
+ *
+ * @param options An options object with out* params already populated (run through a decode*
+ * method with inJustDecodeBounds==true
+ * @param reqWidth The requested width of the resulting bitmap
+ * @param reqHeight The requested height of the resulting bitmap
+ * @return The value to be used for inSampleSize
+ */
+ public static int calculateInSampleSize(BitmapFactory.Options options,
+ int reqWidth, int reqHeight) {
+ // BEGIN_INCLUDE (calculate_sample_size)
+ // Raw height and width of image
+ final int height = options.outHeight;
+ final int width = options.outWidth;
+ int inSampleSize = 1;
+
+ if (height > reqHeight || width > reqWidth) {
+
+ final int halfHeight = height / 2;
+ final int halfWidth = width / 2;
+
+ // Calculate the largest inSampleSize value that is a power of 2 and keeps both
+ // height and width larger than the requested height and width.
+ while ((halfHeight / inSampleSize) > reqHeight
+ && (halfWidth / inSampleSize) > reqWidth) {
+ inSampleSize *= 2;
+ }
+
+ // This offers some additional logic in case the image has a strange
+ // aspect ratio. For example, a panorama may have a much larger
+ // width than height. In these cases the total pixels might still
+ // end up being too large to fit comfortably in memory, so we should
+ // be more aggressive with sample down the image (=larger inSampleSize).
+
+ long totalPixels = width * height / inSampleSize;
+
+ // Anything more than 2x the requested pixels we'll sample down further
+ final long totalReqPixelsCap = reqWidth * reqHeight * 2;
+
+ while (totalPixels > totalReqPixelsCap) {
+ inSampleSize *= 2;
+ totalPixels /= 2;
+ }
+ }
+ return inSampleSize;
+ // END_INCLUDE (calculate_sample_size)
+ }
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/ImageWorker.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/ImageWorker.java
new file mode 100644
index 0000000..f44d00d
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/ImageWorker.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.displayingbitmaps.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.TransitionDrawable;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.widget.ImageView;
+
+import com.example.android.common.logger.Log;
+import com.example.android.displayingbitmaps.BuildConfig;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * This class wraps up completing some arbitrary long running work when loading a bitmap to an
+ * ImageView. It handles things like using a memory and disk cache, running the work in a background
+ * thread and setting a placeholder image.
+ */
+public abstract class ImageWorker {
+ private static final String TAG = "ImageWorker";
+ private static final int FADE_IN_TIME = 200;
+
+ private ImageCache mImageCache;
+ private ImageCache.ImageCacheParams mImageCacheParams;
+ private Bitmap mLoadingBitmap;
+ private boolean mFadeInBitmap = true;
+ private boolean mExitTasksEarly = false;
+ protected boolean mPauseWork = false;
+ private final Object mPauseWorkLock = new Object();
+
+ protected Resources mResources;
+
+ private static final int MESSAGE_CLEAR = 0;
+ private static final int MESSAGE_INIT_DISK_CACHE = 1;
+ private static final int MESSAGE_FLUSH = 2;
+ private static final int MESSAGE_CLOSE = 3;
+
+ protected ImageWorker(Context context) {
+ mResources = context.getResources();
+ }
+
+ /**
+ * Load an image specified by the data parameter into an ImageView (override
+ * {@link ImageWorker#processBitmap(Object)} to define the processing logic). A memory and
+ * disk cache will be used if an {@link ImageCache} has been added using
+ * {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCache.ImageCacheParams)}. If the
+ * image is found in the memory cache, it is set immediately, otherwise an {@link AsyncTask}
+ * will be created to asynchronously load the bitmap.
+ *
+ * @param data The URL of the image to download.
+ * @param imageView The ImageView to bind the downloaded image to.
+ */
+ public void loadImage(Object data, ImageView imageView) {
+ if (data == null) {
+ return;
+ }
+
+ BitmapDrawable value = null;
+
+ if (mImageCache != null) {
+ value = mImageCache.getBitmapFromMemCache(String.valueOf(data));
+ }
+
+ if (value != null) {
+ // Bitmap found in memory cache
+ imageView.setImageDrawable(value);
+ } else if (cancelPotentialWork(data, imageView)) {
+ //BEGIN_INCLUDE(execute_background_task)
+ final BitmapWorkerTask task = new BitmapWorkerTask(data, imageView);
+ final AsyncDrawable asyncDrawable =
+ new AsyncDrawable(mResources, mLoadingBitmap, task);
+ imageView.setImageDrawable(asyncDrawable);
+
+ // NOTE: This uses a custom version of AsyncTask that has been pulled from the
+ // framework and slightly modified. Refer to the docs at the top of the class
+ // for more info on what was changed.
+ task.executeOnExecutor(AsyncTask.DUAL_THREAD_EXECUTOR);
+ //END_INCLUDE(execute_background_task)
+ }
+ }
+
+ /**
+ * Set placeholder bitmap that shows when the the background thread is running.
+ *
+ * @param bitmap
+ */
+ public void setLoadingImage(Bitmap bitmap) {
+ mLoadingBitmap = bitmap;
+ }
+
+ /**
+ * Set placeholder bitmap that shows when the the background thread is running.
+ *
+ * @param resId
+ */
+ public void setLoadingImage(int resId) {
+ mLoadingBitmap = BitmapFactory.decodeResource(mResources, resId);
+ }
+
+ /**
+ * Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap
+ * caching.
+ * @param fragmentManager
+ * @param cacheParams The cache parameters to use for the image cache.
+ */
+ public void addImageCache(FragmentManager fragmentManager,
+ ImageCache.ImageCacheParams cacheParams) {
+ mImageCacheParams = cacheParams;
+ mImageCache = ImageCache.getInstance(fragmentManager, mImageCacheParams);
+ new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
+ }
+
+ /**
+ * Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap
+ * caching.
+ * @param activity
+ * @param diskCacheDirectoryName See
+ * {@link ImageCache.ImageCacheParams#ImageCacheParams(android.content.Context, String)}.
+ */
+ public void addImageCache(FragmentActivity activity, String diskCacheDirectoryName) {
+ mImageCacheParams = new ImageCache.ImageCacheParams(activity, diskCacheDirectoryName);
+ mImageCache = ImageCache.getInstance(activity.getSupportFragmentManager(), mImageCacheParams);
+ new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
+ }
+
+ /**
+ * If set to true, the image will fade-in once it has been loaded by the background thread.
+ */
+ public void setImageFadeIn(boolean fadeIn) {
+ mFadeInBitmap = fadeIn;
+ }
+
+ public void setExitTasksEarly(boolean exitTasksEarly) {
+ mExitTasksEarly = exitTasksEarly;
+ setPauseWork(false);
+ }
+
+ /**
+ * Subclasses should override this to define any processing or work that must happen to produce
+ * the final bitmap. This will be executed in a background thread and be long running. For
+ * example, you could resize a large bitmap here, or pull down an image from the network.
+ *
+ * @param data The data to identify which image to process, as provided by
+ * {@link ImageWorker#loadImage(Object, android.widget.ImageView)}
+ * @return The processed bitmap
+ */
+ protected abstract Bitmap processBitmap(Object data);
+
+ /**
+ * @return The {@link ImageCache} object currently being used by this ImageWorker.
+ */
+ protected ImageCache getImageCache() {
+ return mImageCache;
+ }
+
+ /**
+ * Cancels any pending work attached to the provided ImageView.
+ * @param imageView
+ */
+ public static void cancelWork(ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+ if (bitmapWorkerTask != null) {
+ bitmapWorkerTask.cancel(true);
+ if (BuildConfig.DEBUG) {
+ final Object bitmapData = bitmapWorkerTask.mData;
+ Log.d(TAG, "cancelWork - cancelled work for " + bitmapData);
+ }
+ }
+ }
+
+ /**
+ * Returns true if the current work has been canceled or if there was no work in
+ * progress on this image view.
+ * Returns false if the work in progress deals with the same data. The work is not
+ * stopped in that case.
+ */
+ public static boolean cancelPotentialWork(Object data, ImageView imageView) {
+ //BEGIN_INCLUDE(cancel_potential_work)
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final Object bitmapData = bitmapWorkerTask.mData;
+ if (bitmapData == null || !bitmapData.equals(data)) {
+ bitmapWorkerTask.cancel(true);
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "cancelPotentialWork - cancelled work for " + data);
+ }
+ } else {
+ // The same work is already in progress.
+ return false;
+ }
+ }
+ return true;
+ //END_INCLUDE(cancel_potential_work)
+ }
+
+ /**
+ * @param imageView Any imageView
+ * @return Retrieve the currently active work task (if any) associated with this imageView.
+ * null if there is no such task.
+ */
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * The actual AsyncTask that will asynchronously process the image.
+ */
+ private class BitmapWorkerTask extends AsyncTask<Void, Void, BitmapDrawable> {
+ private Object mData;
+ private final WeakReference<ImageView> imageViewReference;
+
+ public BitmapWorkerTask(Object data, ImageView imageView) {
+ mData = data;
+ imageViewReference = new WeakReference<ImageView>(imageView);
+ }
+
+ /**
+ * Background processing.
+ */
+ @Override
+ protected BitmapDrawable doInBackground(Void... params) {
+ //BEGIN_INCLUDE(load_bitmap_in_background)
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "doInBackground - starting work");
+ }
+
+ final String dataString = String.valueOf(mData);
+ Bitmap bitmap = null;
+ BitmapDrawable drawable = null;
+
+ // Wait here if work is paused and the task is not cancelled
+ synchronized (mPauseWorkLock) {
+ while (mPauseWork && !isCancelled()) {
+ try {
+ mPauseWorkLock.wait();
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ // If the image cache is available and this task has not been cancelled by another
+ // thread and the ImageView that was originally bound to this task is still bound back
+ // to this task and our "exit early" flag is not set then try and fetch the bitmap from
+ // the cache
+ if (mImageCache != null && !isCancelled() && getAttachedImageView() != null
+ && !mExitTasksEarly) {
+ bitmap = mImageCache.getBitmapFromDiskCache(dataString);
+ }
+
+ // If the bitmap was not found in the cache and this task has not been cancelled by
+ // another thread and the ImageView that was originally bound to this task is still
+ // bound back to this task and our "exit early" flag is not set, then call the main
+ // process method (as implemented by a subclass)
+ if (bitmap == null && !isCancelled() && getAttachedImageView() != null
+ && !mExitTasksEarly) {
+ bitmap = processBitmap(mData);
+ }
+
+ // If the bitmap was processed and the image cache is available, then add the processed
+ // bitmap to the cache for future use. Note we don't check if the task was cancelled
+ // here, if it was, and the thread is still running, we may as well add the processed
+ // bitmap to our cache as it might be used again in the future
+ if (bitmap != null) {
+ if (Utils.hasHoneycomb()) {
+ // Running on Honeycomb or newer, so wrap in a standard BitmapDrawable
+ drawable = new BitmapDrawable(mResources, bitmap);
+ } else {
+ // Running on Gingerbread or older, so wrap in a RecyclingBitmapDrawable
+ // which will recycle automagically
+ drawable = new RecyclingBitmapDrawable(mResources, bitmap);
+ }
+
+ if (mImageCache != null) {
+ mImageCache.addBitmapToCache(dataString, drawable);
+ }
+ }
+
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "doInBackground - finished work");
+ }
+
+ return drawable;
+ //END_INCLUDE(load_bitmap_in_background)
+ }
+
+ /**
+ * Once the image is processed, associates it to the imageView
+ */
+ @Override
+ protected void onPostExecute(BitmapDrawable value) {
+ //BEGIN_INCLUDE(complete_background_work)
+ // if cancel was called on this task or the "exit early" flag is set then we're done
+ if (isCancelled() || mExitTasksEarly) {
+ value = null;
+ }
+
+ final ImageView imageView = getAttachedImageView();
+ if (value != null && imageView != null) {
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "onPostExecute - setting bitmap");
+ }
+ setImageDrawable(imageView, value);
+ }
+ //END_INCLUDE(complete_background_work)
+ }
+
+ @Override
+ protected void onCancelled(BitmapDrawable value) {
+ super.onCancelled(value);
+ synchronized (mPauseWorkLock) {
+ mPauseWorkLock.notifyAll();
+ }
+ }
+
+ /**
+ * Returns the ImageView associated with this task as long as the ImageView's task still
+ * points to this task as well. Returns null otherwise.
+ */
+ private ImageView getAttachedImageView() {
+ final ImageView imageView = imageViewReference.get();
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (this == bitmapWorkerTask) {
+ return imageView;
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * A custom Drawable that will be attached to the imageView while the work is in progress.
+ * Contains a reference to the actual worker task, so that it can be stopped if a new binding is
+ * required, and makes sure that only the last started worker process can bind its result,
+ * independently of the finish order.
+ */
+ private static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+ public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference =
+ new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
+ }
+
+ public BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+
+ /**
+ * Called when the processing is complete and the final drawable should be
+ * set on the ImageView.
+ *
+ * @param imageView
+ * @param drawable
+ */
+ private void setImageDrawable(ImageView imageView, Drawable drawable) {
+ if (mFadeInBitmap) {
+ // Transition drawable with a transparent drawable and the final drawable
+ final TransitionDrawable td =
+ new TransitionDrawable(new Drawable[] {
+ new ColorDrawable(android.R.color.transparent),
+ drawable
+ });
+ // Set background to loading bitmap
+ imageView.setBackgroundDrawable(
+ new BitmapDrawable(mResources, mLoadingBitmap));
+
+ imageView.setImageDrawable(td);
+ td.startTransition(FADE_IN_TIME);
+ } else {
+ imageView.setImageDrawable(drawable);
+ }
+ }
+
+ /**
+ * Pause any ongoing background work. This can be used as a temporary
+ * measure to improve performance. For example background work could
+ * be paused when a ListView or GridView is being scrolled using a
+ * {@link android.widget.AbsListView.OnScrollListener} to keep
+ * scrolling smooth.
+ * <p>
+ * If work is paused, be sure setPauseWork(false) is called again
+ * before your fragment or activity is destroyed (for example during
+ * {@link android.app.Activity#onPause()}), or there is a risk the
+ * background thread will never finish.
+ */
+ public void setPauseWork(boolean pauseWork) {
+ synchronized (mPauseWorkLock) {
+ mPauseWork = pauseWork;
+ if (!mPauseWork) {
+ mPauseWorkLock.notifyAll();
+ }
+ }
+ }
+
+ protected class CacheAsyncTask extends AsyncTask<Object, Void, Void> {
+
+ @Override
+ protected Void doInBackground(Object... params) {
+ switch ((Integer)params[0]) {
+ case MESSAGE_CLEAR:
+ clearCacheInternal();
+ break;
+ case MESSAGE_INIT_DISK_CACHE:
+ initDiskCacheInternal();
+ break;
+ case MESSAGE_FLUSH:
+ flushCacheInternal();
+ break;
+ case MESSAGE_CLOSE:
+ closeCacheInternal();
+ break;
+ }
+ return null;
+ }
+ }
+
+ protected void initDiskCacheInternal() {
+ if (mImageCache != null) {
+ mImageCache.initDiskCache();
+ }
+ }
+
+ protected void clearCacheInternal() {
+ if (mImageCache != null) {
+ mImageCache.clearCache();
+ }
+ }
+
+ protected void flushCacheInternal() {
+ if (mImageCache != null) {
+ mImageCache.flush();
+ }
+ }
+
+ protected void closeCacheInternal() {
+ if (mImageCache != null) {
+ mImageCache.close();
+ mImageCache = null;
+ }
+ }
+
+ public void clearCache() {
+ new CacheAsyncTask().execute(MESSAGE_CLEAR);
+ }
+
+ public void flushCache() {
+ new CacheAsyncTask().execute(MESSAGE_FLUSH);
+ }
+
+ public void closeCache() {
+ new CacheAsyncTask().execute(MESSAGE_CLOSE);
+ }
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/RecyclingBitmapDrawable.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/RecyclingBitmapDrawable.java
new file mode 100644
index 0000000..5534a6a
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/RecyclingBitmapDrawable.java
@@ -0,0 +1,109 @@
+/*
+ * 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 com.example.android.displayingbitmaps.util;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+
+import com.example.android.common.logger.Log;
+import com.example.android.displayingbitmaps.BuildConfig;
+
+/**
+ * A BitmapDrawable that keeps track of whether it is being displayed or cached.
+ * When the drawable is no longer being displayed or cached,
+ * {@link android.graphics.Bitmap#recycle() recycle()} will be called on this drawable's bitmap.
+ */
+public class RecyclingBitmapDrawable extends BitmapDrawable {
+
+ static final String TAG = "CountingBitmapDrawable";
+
+ private int mCacheRefCount = 0;
+ private int mDisplayRefCount = 0;
+
+ private boolean mHasBeenDisplayed;
+
+ public RecyclingBitmapDrawable(Resources res, Bitmap bitmap) {
+ super(res, bitmap);
+ }
+
+ /**
+ * Notify the drawable that the displayed state has changed. Internally a
+ * count is kept so that the drawable knows when it is no longer being
+ * displayed.
+ *
+ * @param isDisplayed - Whether the drawable is being displayed or not
+ */
+ public void setIsDisplayed(boolean isDisplayed) {
+ //BEGIN_INCLUDE(set_is_displayed)
+ synchronized (this) {
+ if (isDisplayed) {
+ mDisplayRefCount++;
+ mHasBeenDisplayed = true;
+ } else {
+ mDisplayRefCount--;
+ }
+ }
+
+ // Check to see if recycle() can be called
+ checkState();
+ //END_INCLUDE(set_is_displayed)
+ }
+
+ /**
+ * Notify the drawable that the cache state has changed. Internally a count
+ * is kept so that the drawable knows when it is no longer being cached.
+ *
+ * @param isCached - Whether the drawable is being cached or not
+ */
+ public void setIsCached(boolean isCached) {
+ //BEGIN_INCLUDE(set_is_cached)
+ synchronized (this) {
+ if (isCached) {
+ mCacheRefCount++;
+ } else {
+ mCacheRefCount--;
+ }
+ }
+
+ // Check to see if recycle() can be called
+ checkState();
+ //END_INCLUDE(set_is_cached)
+ }
+
+ private synchronized void checkState() {
+ //BEGIN_INCLUDE(check_state)
+ // If the drawable cache and display ref counts = 0, and this drawable
+ // has been displayed, then recycle
+ if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
+ && hasValidBitmap()) {
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "No longer being used or cached so recycling. "
+ + toString());
+ }
+
+ getBitmap().recycle();
+ }
+ //END_INCLUDE(check_state)
+ }
+
+ private synchronized boolean hasValidBitmap() {
+ Bitmap bitmap = getBitmap();
+ return bitmap != null && !bitmap.isRecycled();
+ }
+
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/Utils.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/Utils.java
new file mode 100644
index 0000000..505d0ef
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/Utils.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.displayingbitmaps.util;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.StrictMode;
+
+import com.example.android.displayingbitmaps.ui.ImageDetailActivity;
+import com.example.android.displayingbitmaps.ui.ImageGridActivity;
+
+/**
+ * Class containing some static utility methods.
+ */
+public class Utils {
+ private Utils() {};
+
+
+ @TargetApi(VERSION_CODES.HONEYCOMB)
+ public static void enableStrictMode() {
+ if (Utils.hasGingerbread()) {
+ StrictMode.ThreadPolicy.Builder threadPolicyBuilder =
+ new StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyLog();
+ StrictMode.VmPolicy.Builder vmPolicyBuilder =
+ new StrictMode.VmPolicy.Builder()
+ .detectAll()
+ .penaltyLog();
+
+ if (Utils.hasHoneycomb()) {
+ threadPolicyBuilder.penaltyFlashScreen();
+ vmPolicyBuilder
+ .setClassInstanceLimit(ImageGridActivity.class, 1)
+ .setClassInstanceLimit(ImageDetailActivity.class, 1);
+ }
+ StrictMode.setThreadPolicy(threadPolicyBuilder.build());
+ StrictMode.setVmPolicy(vmPolicyBuilder.build());
+ }
+ }
+
+ public static boolean hasFroyo() {
+ // Can use static final constants like FROYO, declared in later versions
+ // of the OS since they are inlined at compile time. This is guaranteed behavior.
+ return Build.VERSION.SDK_INT >= VERSION_CODES.FROYO;
+ }
+
+ public static boolean hasGingerbread() {
+ return Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD;
+ }
+
+ public static boolean hasHoneycomb() {
+ return Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+ }
+
+ public static boolean hasHoneycombMR1() {
+ return Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1;
+ }
+
+ public static boolean hasJellyBean() {
+ return Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+ }
+
+ public static boolean hasKitKat() {
+ return Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT;
+ }
+}
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-hdpi/ic_launcher.png b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..75b3c97
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-hdpi/tile.9.png b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-hdpi/tile.9.png
new file mode 100644
index 0000000..1358628
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-hdpi/tile.9.png
Binary files differ
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-mdpi/ic_launcher.png b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..0c9c11a
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-nodpi/empty_photo.png b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-nodpi/empty_photo.png
new file mode 100644
index 0000000..da1478a
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-nodpi/empty_photo.png
Binary files differ
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-xhdpi/ic_launcher.png b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..7c5aeed
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-xxhdpi/ic_launcher.png b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..91b0f96
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable/photogrid_list_selector.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable/photogrid_list_selector.xml
new file mode 100644
index 0000000..d331c39
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/drawable/photogrid_list_selector.xml
@@ -0,0 +1,33 @@
+<!--
+ Copyright (C) 2012 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="true">
+ <shape>
+ <solid android:color="@color/grid_state_pressed" />
+ </shape>
+ </item>
+
+ <item android:state_focused="true">
+ <shape>
+ <solid android:color="@color/grid_state_focused" />
+ </shape>
+ </item>
+
+ <item android:drawable="@android:color/transparent" />
+
+</selector>
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/layout/activity_main.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/layout/activity_main.xml
new file mode 100755
index 0000000..be1aa49
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,36 @@
+<!--
+ Copyright 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout style="@style/Widget.SampleMessageTile"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView style="@style/Widget.SampleMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/horizontal_page_margin"
+ android:layout_marginRight="@dimen/horizontal_page_margin"
+ android:layout_marginTop="@dimen/vertical_page_margin"
+ android:layout_marginBottom="@dimen/vertical_page_margin"
+ android:text="@string/intro_message" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/layout/image_detail_fragment.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/layout/image_detail_fragment.xml
new file mode 100644
index 0000000..97ac520
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/layout/image_detail_fragment.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" >
+
+ <ProgressBar
+ style="?android:attr/progressBarStyleLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center" />
+
+ <com.example.android.displayingbitmaps.ui.RecyclingImageView
+ android:id="@+id/imageView"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:contentDescription="@string/imageview_description" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/layout/image_detail_pager.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/layout/image_detail_pager.xml
new file mode 100644
index 0000000..877a26b
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/layout/image_detail_pager.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 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.
+-->
+
+<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pager"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" >
+
+</android.support.v4.view.ViewPager>
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/layout/image_grid_fragment.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/layout/image_grid_fragment.xml
new file mode 100644
index 0000000..e2034de
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/layout/image_grid_fragment.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 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.
+-->
+
+<GridView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/gridView"
+ style="@style/PhotoGridLayout"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:columnWidth="@dimen/image_thumbnail_size"
+ android:horizontalSpacing="@dimen/image_thumbnail_spacing"
+ android:numColumns="auto_fit"
+ android:stretchMode="columnWidth"
+ android:verticalSpacing="@dimen/image_thumbnail_spacing" >
+
+</GridView>
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/menu/main_menu.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/menu/main_menu.xml
new file mode 100644
index 0000000..35dad09
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/menu/main_menu.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item
+ android:id="@+id/clear_cache"
+ android:icon="@android:drawable/ic_menu_delete"
+ android:showAsAction="never"
+ android:title="@string/clear_cache_menu"/>
+
+</menu>
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-large/dimens.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-large/dimens.xml
new file mode 100644
index 0000000..c681876
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-large/dimens.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 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.
+-->
+
+<resources>
+
+ <dimen name="image_thumbnail_size">148dp</dimen>
+ <dimen name="image_thumbnail_spacing">2dp</dimen>
+
+</resources>
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-sw600dp/template-dimens.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-sw600dp/template-dimens.xml
new file mode 100644
index 0000000..22074a2
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-sw600dp/template-dimens.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <!-- Semantic definitions -->
+
+ <dimen name="horizontal_page_margin">@dimen/margin_huge</dimen>
+ <dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
+
+</resources>
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-sw600dp/template-styles.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-sw600dp/template-styles.xml
new file mode 100644
index 0000000..03d1974
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-sw600dp/template-styles.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <style name="Widget.SampleMessage">
+ <item name="android:textAppearance">?android:textAppearanceLarge</item>
+ <item name="android:lineSpacingMultiplier">1.2</item>
+ <item name="android:shadowDy">-6.5</item>
+ </style>
+
+</resources>
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-v11/styles.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-v11/styles.xml
new file mode 100644
index 0000000..57da0fb
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-v11/styles.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 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.
+-->
+
+<resources>
+
+ <style name="AppThemeDark" parent="@android:style/Theme.Holo">
+ <item name="android:windowActionBarOverlay">true</item>
+ <item name="android:windowBackground">@android:color/black</item>
+ <item name="android:actionBarStyle">@style/TranslucentDarkActionBar</item>
+ </style>
+
+ <style name="AppThemeDark.FullScreen" />
+
+ <style name="TranslucentDarkActionBar" parent="@android:style/Widget.Holo.ActionBar">
+ <item name="android:background">#99000000</item>
+ </style>
+
+ <!--<style name="PhotoGridLayout">-->
+ <!--<item name="android:drawSelectorOnTop">true</item>-->
+ <!--</style>-->
+
+</resources>
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-xlarge/dimens.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-xlarge/dimens.xml
new file mode 100644
index 0000000..0f43977
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values-xlarge/dimens.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 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.
+-->
+
+<resources>
+
+ <dimen name="image_thumbnail_size">198dp</dimen>
+ <dimen name="image_thumbnail_spacing">2dp</dimen>
+
+</resources>
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/base-strings.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/base-strings.xml
new file mode 100644
index 0000000..a6a8390
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/base-strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 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.
+-->
+
+
+
+<resources>
+ <string name="app_name">DisplayingBitmaps</string>
+ <string name="intro_message">
+ <![CDATA[
+
+
+ This is a sample application for the Android Training class
+ "Displaying Bitmaps Efficiently"
+ (http://developer.android.com/training/displaying-bitmaps/).\n\n
+
+ It demonstrates how to load large bitmaps efficiently off the main UI thread, caching
+ bitmaps (both in memory and on disk), managing bitmap memory and displaying bitmaps
+ in UI elements such as ViewPager and ListView/GridView.
+
+
+ ]]>
+ </string>
+</resources>
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/colors.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/colors.xml
new file mode 100644
index 0000000..521b4b9
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/colors.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 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.
+-->
+
+<resources>
+
+ <color name="grid_state_pressed">#1Affffff</color>
+ <color name="grid_state_focused">#80000000</color>
+
+</resources>
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/dimens.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..53f61f0
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/dimens.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 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.
+-->
+
+<resources>
+
+ <dimen name="image_thumbnail_size">100dp</dimen>
+ <dimen name="image_thumbnail_spacing">1dp</dimen>
+
+</resources>
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/strings.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/strings.xml
new file mode 100644
index 0000000..84b6137
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 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.
+-->
+
+<resources>
+
+ <string name="clear_cache_menu">Clear Caches</string>
+ <string name="clear_cache_complete_toast">Caches have been cleared</string>
+ <string name="imageview_description">Image Thumbnail</string>
+ <string name="no_network_connection_toast">No network connection found</string>
+
+</resources>
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/styles.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/styles.xml
new file mode 100644
index 0000000..e9aabee
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/styles.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 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.
+-->
+
+<resources>
+
+ <style name="AppThemeDark" parent="android:Theme" />
+
+ <style name="AppThemeDark.FullScreen" parent="@android:style/Theme.Black.NoTitleBar.Fullscreen" />
+
+ <style name="PhotoGridLayout">
+ <item name="android:drawSelectorOnTop">true</item>
+ <item name="android:listSelector">@drawable/photogrid_list_selector</item>
+ </style>
+
+</resources>
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/template-dimens.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/template-dimens.xml
new file mode 100644
index 0000000..39e710b
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/template-dimens.xml
@@ -0,0 +1,32 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <!-- Define standard dimensions to comply with Holo-style grids and rhythm. -->
+
+ <dimen name="margin_tiny">4dp</dimen>
+ <dimen name="margin_small">8dp</dimen>
+ <dimen name="margin_medium">16dp</dimen>
+ <dimen name="margin_large">32dp</dimen>
+ <dimen name="margin_huge">64dp</dimen>
+
+ <!-- Semantic definitions -->
+
+ <dimen name="horizontal_page_margin">@dimen/margin_medium</dimen>
+ <dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
+
+</resources>
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/template-styles.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/template-styles.xml
new file mode 100644
index 0000000..404623e
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/src/main/res/values/template-styles.xml
@@ -0,0 +1,42 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <!-- Activity themes -->
+
+ <style name="Theme.Base" parent="android:Theme.Holo.Light" />
+
+ <style name="Theme.Sample" parent="Theme.Base" />
+
+ <style name="AppTheme" parent="Theme.Sample" />
+ <!-- Widget styling -->
+
+ <style name="Widget" />
+
+ <style name="Widget.SampleMessage">
+ <item name="android:textAppearance">?android:textAppearanceMedium</item>
+ <item name="android:lineSpacingMultiplier">1.1</item>
+ </style>
+
+ <style name="Widget.SampleMessageTile">
+ <item name="android:background">@drawable/tile</item>
+ <item name="android:shadowColor">#7F000000</item>
+ <item name="android:shadowDy">-3.5</item>
+ <item name="android:shadowRadius">2</item>
+ </style>
+
+</resources>
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/tests/AndroidManifest.xml b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/tests/AndroidManifest.xml
new file mode 100644
index 0000000..4ddcbbb
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/tests/AndroidManifest.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 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.
+-->
+
+
+
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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 name must be unique so suffix with "tests" so package loader doesn't ignore us -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.displayingbitmaps.tests"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk
+ android:minSdkVersion="18"
+ android:targetSdkVersion="19" />
+
+ <!-- We add an application tag here just so that we can indicate that
+ this package needs to link against the android.test library,
+ which is needed when building test cases. -->
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <!--
+ Specifies the instrumentation test runner used to run the tests.
+ -->
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.example.android.displayingbitmaps"
+ android:label="Tests for com.example.android.displayingbitmaps" />
+
+</manifest>
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/tests/src/com/example/android/displayingbitmaps/tests/SampleTests.java b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/tests/src/com/example/android/displayingbitmaps/tests/SampleTests.java
new file mode 100644
index 0000000..f295856
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/DisplayingBitmapsSample/tests/src/com/example/android/displayingbitmaps/tests/SampleTests.java
@@ -0,0 +1,79 @@
+/*
+* Copyright 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.
+*/
+
+
+
+/*
+* 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 com.example.android.displayingbitmaps.tests;
+
+import com.example.android.displayingbitmaps.*;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+/**
+* Tests for DisplayingBitmaps sample.
+*/
+public class SampleTests extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private MainActivity mTestActivity;
+ private DisplayingBitmapsFragment mTestFragment;
+
+ public SampleTests() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // Starts the activity under test using the default Intent with:
+ // action = {@link Intent#ACTION_MAIN}
+ // flags = {@link Intent#FLAG_ACTIVITY_NEW_TASK}
+ // All other fields are null or empty.
+ mTestActivity = getActivity();
+ mTestFragment = (DisplayingBitmapsFragment)
+ mTestActivity.getSupportFragmentManager().getFragments().get(1);
+ }
+
+ /**
+ * Test if the test fixture has been set up correctly.
+ */
+ public void testPreconditions() {
+ //Try to add a message to add context to your assertions. These messages will be shown if
+ //a tests fails and make it easy to understand why a test failed
+ assertNotNull("mTestActivity is null", mTestActivity);
+ assertNotNull("mTestFragment is null", mTestFragment);
+ }
+
+ /**
+ * Add more tests below.
+ */
+
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/DisplayingBitmaps/README.txt b/prebuilts/gradle/DisplayingBitmaps/README.txt
new file mode 100644
index 0000000..9616c58
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/README.txt
@@ -0,0 +1,18 @@
+Build Instructions
+-------------------
+
+This sample uses the Gradle build system. To build this project, use the
+"gradlew build" command or use "Import Project" in Android Studio.
+
+To see a list of all available commands, run "gradlew tasks".
+
+Dependencies
+-------------
+
+- Android SDK Build-tools v18.1
+- Android Support Repository v2
+
+Dependencies are available for download via the Android SDK Manager.
+
+Android Studio is available for download at:
+ http://developer.android.com/sdk/installing/studio.html
diff --git a/prebuilts/gradle/DisplayingBitmaps/build.gradle b/prebuilts/gradle/DisplayingBitmaps/build.gradle
new file mode 100644
index 0000000..5cf5d3d
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/build.gradle
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/prebuilts/gradle/DisplayingBitmaps/gradle/wrapper/gradle-wrapper.jar b/prebuilts/gradle/DisplayingBitmaps/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/prebuilts/gradle/DisplayingBitmaps/gradle/wrapper/gradle-wrapper.properties b/prebuilts/gradle/DisplayingBitmaps/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..56f685a
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip
diff --git a/prebuilts/gradle/DisplayingBitmaps/gradlew b/prebuilts/gradle/DisplayingBitmaps/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/prebuilts/gradle/DisplayingBitmaps/gradlew.bat b/prebuilts/gradle/DisplayingBitmaps/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/prebuilts/gradle/DisplayingBitmaps/settings.gradle b/prebuilts/gradle/DisplayingBitmaps/settings.gradle
new file mode 100644
index 0000000..a5c5699
--- /dev/null
+++ b/prebuilts/gradle/DisplayingBitmaps/settings.gradle
@@ -0,0 +1,4 @@
+
+
+
+include 'DisplayingBitmapsSample'
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/build.gradle b/prebuilts/gradle/MediaRouter/MediaRouterSample/build.gradle
new file mode 100644
index 0000000..9216516
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/build.gradle
@@ -0,0 +1,64 @@
+
+
+
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.8.+'
+ }
+}
+
+apply plugin: 'android'
+
+dependencies {
+
+ // Add the support lib that is appropriate for SDK 4
+ compile "com.android.support:support-v4:19.0.+"
+
+
+ compile "com.android.support:appcompat-v7:+"
+ compile "com.android.support:mediarouter-v7:+"
+}
+
+// The sample build uses multiple directories to
+// keep boilerplate and common code separate from
+// the main sample code.
+List<String> dirs = [
+ 'main', // main sample code; look here for the interesting stuff.
+ 'common', // components that are reused by multiple samples
+ 'template'] // boilerplate code that is generated by the sample template process
+
+android {
+ compileSdkVersion 19
+
+ buildToolsVersion "19.0.1"
+
+ sourceSets {
+ main {
+ dirs.each { dir ->
+ java.srcDirs "src/${dir}/java"
+ res.srcDirs "src/${dir}/res"
+ }
+ }
+ instrumentTest.setRoot('tests')
+ instrumentTest.java.srcDirs = ['tests/src']
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/AndroidManifest.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0b5bec4
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/AndroidManifest.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Declare the contents of this Android application. The namespace
+ attribute brings in the Android platform namespace, and the package
+ supplies a unique name for the application. When writing your
+ own application, the package name must be changed from "com.example.*"
+ to come from a domain that you own or have control over. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.mediarouter">
+ <!-- Permission for INTERNET is required for streaming video content
+ from the web, it's not required otherwise. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <!-- Permission for SYSTEM_ALERT_WINDOW is only required for emulating
+ remote display using system alert window. -->
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+ <uses-sdk android:targetSdkVersion="19"
+ android:minSdkVersion="7"/>
+
+ <!-- The smallest screen this app works on is a phone. The app will
+ scale its UI to larger screens but doesn't make good use of them
+ so allow the compatibility mode button to be shown (mostly because
+ this is just convenient for testing). -->
+ <supports-screens android:requiresSmallestWidthDp="320"
+ android:compatibleWidthLimitDp="480" />
+
+ <application android:label="@string/app_name"
+ android:icon="@drawable/ic_launcher"
+ android:hardwareAccelerated="true">
+
+ <receiver android:name=".player.SampleMediaButtonReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.MEDIA_BUTTON" />
+ </intent-filter>
+ </receiver>
+ <!-- MediaRouter Support Samples -->
+
+ <activity android:name=".player.MainActivity"
+ android:label="@string/app_name"
+ android:theme="@style/Theme.AppCompat.Light">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.example.android.supportv7.SAMPLE_CODE" />
+ </intent-filter>
+ </activity>
+
+ <service android:name=".provider.SampleMediaRouteProviderService"
+ android:label="@string/sample_media_route_provider_service"
+ android:process=":mrp">
+ <intent-filter>
+ <action android:name="android.media.MediaRouteProviderService" />
+ </intent-filter>
+ </service>
+
+ </application>
+</manifest>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/Log.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/Log.java
new file mode 100644
index 0000000..17503c5
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/Log.java
@@ -0,0 +1,236 @@
+/*
+ * 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 com.example.android.common.logger;
+
+/**
+ * Helper class for a list (or tree) of LoggerNodes.
+ *
+ * <p>When this is set as the head of the list,
+ * an instance of it can function as a drop-in replacement for {@link android.util.Log}.
+ * Most of the methods in this class server only to map a method call in Log to its equivalent
+ * in LogNode.</p>
+ */
+public class Log {
+ // Grabbing the native values from Android's native logging facilities,
+ // to make for easy migration and interop.
+ public static final int NONE = -1;
+ public static final int VERBOSE = android.util.Log.VERBOSE;
+ public static final int DEBUG = android.util.Log.DEBUG;
+ public static final int INFO = android.util.Log.INFO;
+ public static final int WARN = android.util.Log.WARN;
+ public static final int ERROR = android.util.Log.ERROR;
+ public static final int ASSERT = android.util.Log.ASSERT;
+
+ // Stores the beginning of the LogNode topology.
+ private static LogNode mLogNode;
+
+ /**
+ * Returns the next LogNode in the linked list.
+ */
+ public static LogNode getLogNode() {
+ return mLogNode;
+ }
+
+ /**
+ * Sets the LogNode data will be sent to.
+ */
+ public static void setLogNode(LogNode node) {
+ mLogNode = node;
+ }
+
+ /**
+ * Instructs the LogNode to print the log data provided. Other LogNodes can
+ * be chained to the end of the LogNode as desired.
+ *
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void println(int priority, String tag, String msg, Throwable tr) {
+ if (mLogNode != null) {
+ mLogNode.println(priority, tag, msg, tr);
+ }
+ }
+
+ /**
+ * Instructs the LogNode to print the log data provided. Other LogNodes can
+ * be chained to the end of the LogNode as desired.
+ *
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ */
+ public static void println(int priority, String tag, String msg) {
+ println(priority, tag, msg, null);
+ }
+
+ /**
+ * Prints a message at VERBOSE priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void v(String tag, String msg, Throwable tr) {
+ println(VERBOSE, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at VERBOSE priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void v(String tag, String msg) {
+ v(tag, msg, null);
+ }
+
+
+ /**
+ * Prints a message at DEBUG priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void d(String tag, String msg, Throwable tr) {
+ println(DEBUG, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at DEBUG priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void d(String tag, String msg) {
+ d(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at INFO priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void i(String tag, String msg, Throwable tr) {
+ println(INFO, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at INFO priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void i(String tag, String msg) {
+ i(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void w(String tag, String msg, Throwable tr) {
+ println(WARN, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void w(String tag, String msg) {
+ w(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void w(String tag, Throwable tr) {
+ w(tag, null, tr);
+ }
+
+ /**
+ * Prints a message at ERROR priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void e(String tag, String msg, Throwable tr) {
+ println(ERROR, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at ERROR priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void e(String tag, String msg) {
+ e(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void wtf(String tag, String msg, Throwable tr) {
+ println(ASSERT, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void wtf(String tag, String msg) {
+ wtf(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void wtf(String tag, Throwable tr) {
+ wtf(tag, null, tr);
+ }
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/LogFragment.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/LogFragment.java
new file mode 100644
index 0000000..b302acd
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/LogFragment.java
@@ -0,0 +1,109 @@
+/*
+* Copyright 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.
+*/
+/*
+ * Copyright 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 com.example.android.common.logger;
+
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ScrollView;
+
+/**
+ * Simple fraggment which contains a LogView and uses is to output log data it receives
+ * through the LogNode interface.
+ */
+public class LogFragment extends Fragment {
+
+ private LogView mLogView;
+ private ScrollView mScrollView;
+
+ public LogFragment() {}
+
+ public View inflateViews() {
+ mScrollView = new ScrollView(getActivity());
+ ViewGroup.LayoutParams scrollParams = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ mScrollView.setLayoutParams(scrollParams);
+
+ mLogView = new LogView(getActivity());
+ ViewGroup.LayoutParams logParams = new ViewGroup.LayoutParams(scrollParams);
+ logParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ mLogView.setLayoutParams(logParams);
+ mLogView.setClickable(true);
+ mLogView.setFocusable(true);
+ mLogView.setTypeface(Typeface.MONOSPACE);
+
+ // Want to set padding as 16 dips, setPadding takes pixels. Hooray math!
+ int paddingDips = 16;
+ double scale = getResources().getDisplayMetrics().density;
+ int paddingPixels = (int) ((paddingDips * (scale)) + .5);
+ mLogView.setPadding(paddingPixels, paddingPixels, paddingPixels, paddingPixels);
+ mLogView.setCompoundDrawablePadding(paddingPixels);
+
+ mLogView.setGravity(Gravity.BOTTOM);
+ mLogView.setTextAppearance(getActivity(), android.R.style.TextAppearance_Holo_Medium);
+
+ mScrollView.addView(mLogView);
+ return mScrollView;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ View result = inflateViews();
+
+ mLogView.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
+ }
+ });
+ return result;
+ }
+
+ public LogView getLogView() {
+ return mLogView;
+ }
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/LogNode.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/LogNode.java
new file mode 100644
index 0000000..bc37cab
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/LogNode.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.common.logger;
+
+/**
+ * Basic interface for a logging system that can output to one or more targets.
+ * Note that in addition to classes that will output these logs in some format,
+ * one can also implement this interface over a filter and insert that in the chain,
+ * such that no targets further down see certain data, or see manipulated forms of the data.
+ * You could, for instance, write a "ToHtmlLoggerNode" that just converted all the log data
+ * it received to HTML and sent it along to the next node in the chain, without printing it
+ * anywhere.
+ */
+public interface LogNode {
+
+ /**
+ * Instructs first LogNode in the list to print the log data provided.
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public void println(int priority, String tag, String msg, Throwable tr);
+
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/LogView.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/LogView.java
new file mode 100644
index 0000000..c01542b
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/LogView.java
@@ -0,0 +1,145 @@
+/*
+ * 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 com.example.android.common.logger;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.*;
+import android.widget.TextView;
+
+/** Simple TextView which is used to output log data received through the LogNode interface.
+*/
+public class LogView extends TextView implements LogNode {
+
+ public LogView(Context context) {
+ super(context);
+ }
+
+ public LogView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public LogView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * Formats the log data and prints it out to the LogView.
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ @Override
+ public void println(int priority, String tag, String msg, Throwable tr) {
+
+
+ String priorityStr = null;
+
+ // For the purposes of this View, we want to print the priority as readable text.
+ switch(priority) {
+ case android.util.Log.VERBOSE:
+ priorityStr = "VERBOSE";
+ break;
+ case android.util.Log.DEBUG:
+ priorityStr = "DEBUG";
+ break;
+ case android.util.Log.INFO:
+ priorityStr = "INFO";
+ break;
+ case android.util.Log.WARN:
+ priorityStr = "WARN";
+ break;
+ case android.util.Log.ERROR:
+ priorityStr = "ERROR";
+ break;
+ case android.util.Log.ASSERT:
+ priorityStr = "ASSERT";
+ break;
+ default:
+ break;
+ }
+
+ // Handily, the Log class has a facility for converting a stack trace into a usable string.
+ String exceptionStr = null;
+ if (tr != null) {
+ exceptionStr = android.util.Log.getStackTraceString(tr);
+ }
+
+ // Take the priority, tag, message, and exception, and concatenate as necessary
+ // into one usable line of text.
+ final StringBuilder outputBuilder = new StringBuilder();
+
+ String delimiter = "\t";
+ appendIfNotNull(outputBuilder, priorityStr, delimiter);
+ appendIfNotNull(outputBuilder, tag, delimiter);
+ appendIfNotNull(outputBuilder, msg, delimiter);
+ appendIfNotNull(outputBuilder, exceptionStr, delimiter);
+
+ // In case this was originally called from an AsyncTask or some other off-UI thread,
+ // make sure the update occurs within the UI thread.
+ ((Activity) getContext()).runOnUiThread( (new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // Display the text we just generated within the LogView.
+ appendToLog(outputBuilder.toString());
+ }
+ })));
+
+ if (mNext != null) {
+ mNext.println(priority, tag, msg, tr);
+ }
+ }
+
+ public LogNode getNext() {
+ return mNext;
+ }
+
+ public void setNext(LogNode node) {
+ mNext = node;
+ }
+
+ /** Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since
+ * the logger takes so many arguments that might be null, this method helps cut out some of the
+ * agonizing tedium of writing the same 3 lines over and over.
+ * @param source StringBuilder containing the text to append to.
+ * @param addStr The String to append
+ * @param delimiter The String to separate the source and appended strings. A tab or comma,
+ * for instance.
+ * @return The fully concatenated String as a StringBuilder
+ */
+ private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) {
+ if (addStr != null) {
+ if (addStr.length() == 0) {
+ delimiter = "";
+ }
+
+ return source.append(addStr).append(delimiter);
+ }
+ return source;
+ }
+
+ // The next LogNode in the chain.
+ LogNode mNext;
+
+ /** Outputs the string as a new line of log data in the LogView. */
+ public void appendToLog(String s) {
+ append("\n" + s);
+ }
+
+
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/LogWrapper.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/LogWrapper.java
new file mode 100644
index 0000000..16a9e7b
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/LogWrapper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.common.logger;
+
+import android.util.Log;
+
+/**
+ * Helper class which wraps Android's native Log utility in the Logger interface. This way
+ * normal DDMS output can be one of the many targets receiving and outputting logs simultaneously.
+ */
+public class LogWrapper implements LogNode {
+
+ // For piping: The next node to receive Log data after this one has done its work.
+ private LogNode mNext;
+
+ /**
+ * Returns the next LogNode in the linked list.
+ */
+ public LogNode getNext() {
+ return mNext;
+ }
+
+ /**
+ * Sets the LogNode data will be sent to..
+ */
+ public void setNext(LogNode node) {
+ mNext = node;
+ }
+
+ /**
+ * Prints data out to the console using Android's native log mechanism.
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ @Override
+ public void println(int priority, String tag, String msg, Throwable tr) {
+ // There actually are log methods that don't take a msg parameter. For now,
+ // if that's the case, just convert null to the empty string and move on.
+ String useMsg = msg;
+ if (useMsg == null) {
+ useMsg = "";
+ }
+
+ // If an exeption was provided, convert that exception to a usable string and attach
+ // it to the end of the msg method.
+ if (tr != null) {
+ msg += "\n" + Log.getStackTraceString(tr);
+ }
+
+ // This is functionally identical to Log.x(tag, useMsg);
+ // For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg)
+ Log.println(priority, tag, useMsg);
+
+ // If this isn't the last node in the chain, move things along.
+ if (mNext != null) {
+ mNext.println(priority, tag, msg, tr);
+ }
+ }
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java
new file mode 100644
index 0000000..19967dc
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/common/logger/MessageOnlyLogFilter.java
@@ -0,0 +1,60 @@
+/*
+ * 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 com.example.android.common.logger;
+
+/**
+ * Simple {@link LogNode} filter, removes everything except the message.
+ * Useful for situations like on-screen log output where you don't want a lot of metadata displayed,
+ * just easy-to-read message updates as they're happening.
+ */
+public class MessageOnlyLogFilter implements LogNode {
+
+ LogNode mNext;
+
+ /**
+ * Takes the "next" LogNode as a parameter, to simplify chaining.
+ *
+ * @param next The next LogNode in the pipeline.
+ */
+ public MessageOnlyLogFilter(LogNode next) {
+ mNext = next;
+ }
+
+ public MessageOnlyLogFilter() {
+ }
+
+ @Override
+ public void println(int priority, String tag, String msg, Throwable tr) {
+ if (mNext != null) {
+ getNext().println(Log.NONE, null, msg, null);
+ }
+ }
+
+ /**
+ * Returns the next LogNode in the chain.
+ */
+ public LogNode getNext() {
+ return mNext;
+ }
+
+ /**
+ * Sets the LogNode data will be sent to..
+ */
+ public void setNext(LogNode node) {
+ mNext = node;
+ }
+
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/LocalPlayer.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/LocalPlayer.java
new file mode 100644
index 0000000..86f5100
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/LocalPlayer.java
@@ -0,0 +1,630 @@
+/*
+ * 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 com.example.android.mediarouter.player;
+
+import android.app.Activity;
+import android.app.Presentation;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.media.MediaPlayer;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.support.v7.media.MediaItemStatus;
+import android.support.v7.media.MediaRouter.RouteInfo;
+import android.util.Log;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.example.android.mediarouter.R;
+
+import java.io.IOException;
+
+/**
+ * Handles playback of a single media item using MediaPlayer.
+ */
+public abstract class LocalPlayer extends Player implements
+ MediaPlayer.OnPreparedListener,
+ MediaPlayer.OnCompletionListener,
+ MediaPlayer.OnErrorListener,
+ MediaPlayer.OnSeekCompleteListener {
+ private static final String TAG = "LocalPlayer";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final int STATE_IDLE = 0;
+ private static final int STATE_PLAY_PENDING = 1;
+ private static final int STATE_READY = 2;
+ private static final int STATE_PLAYING = 3;
+ private static final int STATE_PAUSED = 4;
+
+ private final Context mContext;
+ private final Handler mHandler = new Handler();
+ private MediaPlayer mMediaPlayer;
+ private int mState = STATE_IDLE;
+ private int mSeekToPos;
+ private int mVideoWidth;
+ private int mVideoHeight;
+ private Surface mSurface;
+ private SurfaceHolder mSurfaceHolder;
+
+ public LocalPlayer(Context context) {
+ mContext = context;
+
+ // reset media player
+ reset();
+ }
+
+ @Override
+ public boolean isRemotePlayback() {
+ return false;
+ }
+
+ @Override
+ public boolean isQueuingSupported() {
+ return false;
+ }
+
+ @Override
+ public void connect(RouteInfo route) {
+ if (DEBUG) {
+ Log.d(TAG, "connecting to: " + route);
+ }
+ }
+
+ @Override
+ public void release() {
+ if (DEBUG) {
+ Log.d(TAG, "releasing");
+ }
+ // release media player
+ if (mMediaPlayer != null) {
+ mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+ }
+
+ // Player
+ @Override
+ public void play(final PlaylistItem item) {
+ if (DEBUG) {
+ Log.d(TAG, "play: item=" + item);
+ }
+ reset();
+ mSeekToPos = (int)item.getPosition();
+ try {
+ mMediaPlayer.setDataSource(mContext, item.getUri());
+ mMediaPlayer.prepareAsync();
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "MediaPlayer throws IllegalStateException, uri=" + item.getUri());
+ } catch (IOException e) {
+ Log.e(TAG, "MediaPlayer throws IOException, uri=" + item.getUri());
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "MediaPlayer throws IllegalArgumentException, uri=" + item.getUri());
+ } catch (SecurityException e) {
+ Log.e(TAG, "MediaPlayer throws SecurityException, uri=" + item.getUri());
+ }
+ if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) {
+ resume();
+ } else {
+ pause();
+ }
+ }
+
+ @Override
+ public void seek(final PlaylistItem item) {
+ if (DEBUG) {
+ Log.d(TAG, "seek: item=" + item);
+ }
+ int pos = (int)item.getPosition();
+ if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
+ mMediaPlayer.seekTo(pos);
+ mSeekToPos = pos;
+ } else if (mState == STATE_IDLE || mState == STATE_PLAY_PENDING) {
+ // Seek before onPrepared() arrives,
+ // need to performed delayed seek in onPrepared()
+ mSeekToPos = pos;
+ }
+ }
+
+ @Override
+ public void getStatus(final PlaylistItem item, final boolean update) {
+ if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
+ // use mSeekToPos if we're currently seeking (mSeekToPos is reset
+ // when seeking is completed)
+ item.setDuration(mMediaPlayer.getDuration());
+ item.setPosition(mSeekToPos > 0 ?
+ mSeekToPos : mMediaPlayer.getCurrentPosition());
+ item.setTimestamp(SystemClock.elapsedRealtime());
+ }
+ if (update && mCallback != null) {
+ mCallback.onPlaylistReady();
+ }
+ }
+
+ @Override
+ public void pause() {
+ if (DEBUG) {
+ Log.d(TAG, "pause");
+ }
+ if (mState == STATE_PLAYING) {
+ mMediaPlayer.pause();
+ mState = STATE_PAUSED;
+ }
+ }
+
+ @Override
+ public void resume() {
+ if (DEBUG) {
+ Log.d(TAG, "resume");
+ }
+ if (mState == STATE_READY || mState == STATE_PAUSED) {
+ mMediaPlayer.start();
+ mState = STATE_PLAYING;
+ } else if (mState == STATE_IDLE){
+ mState = STATE_PLAY_PENDING;
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (DEBUG) {
+ Log.d(TAG, "stop");
+ }
+ if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
+ mMediaPlayer.stop();
+ mState = STATE_IDLE;
+ }
+ }
+
+ @Override
+ public void enqueue(final PlaylistItem item) {
+ throw new UnsupportedOperationException("LocalPlayer doesn't support enqueue!");
+ }
+
+ @Override
+ public PlaylistItem remove(String iid) {
+ throw new UnsupportedOperationException("LocalPlayer doesn't support remove!");
+ }
+
+ //MediaPlayer Listeners
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ if (DEBUG) {
+ Log.d(TAG, "onPrepared");
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mState == STATE_IDLE) {
+ mState = STATE_READY;
+ updateVideoRect();
+ } else if (mState == STATE_PLAY_PENDING) {
+ mState = STATE_PLAYING;
+ updateVideoRect();
+ if (mSeekToPos > 0) {
+ if (DEBUG) {
+ Log.d(TAG, "seek to initial pos: " + mSeekToPos);
+ }
+ mMediaPlayer.seekTo(mSeekToPos);
+ }
+ mMediaPlayer.start();
+ }
+ if (mCallback != null) {
+ mCallback.onPlaylistChanged();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ if (DEBUG) {
+ Log.d(TAG, "onCompletion");
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mCallback != null) {
+ mCallback.onCompletion();
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ if (DEBUG) {
+ Log.d(TAG, "onError");
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mCallback != null) {
+ mCallback.onError();
+ }
+ }
+ });
+ // return true so that onCompletion is not called
+ return true;
+ }
+
+ @Override
+ public void onSeekComplete(MediaPlayer mp) {
+ if (DEBUG) {
+ Log.d(TAG, "onSeekComplete");
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSeekToPos = 0;
+ if (mCallback != null) {
+ mCallback.onPlaylistChanged();
+ }
+ }
+ });
+ }
+
+ protected Context getContext() { return mContext; }
+ protected MediaPlayer getMediaPlayer() { return mMediaPlayer; }
+ protected int getVideoWidth() { return mVideoWidth; }
+ protected int getVideoHeight() { return mVideoHeight; }
+ protected void setSurface(Surface surface) {
+ mSurface = surface;
+ mSurfaceHolder = null;
+ updateSurface();
+ }
+
+ protected void setSurface(SurfaceHolder surfaceHolder) {
+ mSurface = null;
+ mSurfaceHolder = surfaceHolder;
+ updateSurface();
+ }
+
+ protected void removeSurface(SurfaceHolder surfaceHolder) {
+ if (surfaceHolder == mSurfaceHolder) {
+ setSurface((SurfaceHolder)null);
+ }
+ }
+
+ protected void updateSurface() {
+ if (mMediaPlayer == null) {
+ // just return if media player is already gone
+ return;
+ }
+ if (mSurface != null) {
+ // The setSurface API does not exist until V14+.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ ICSMediaPlayer.setSurface(mMediaPlayer, mSurface);
+ } else {
+ throw new UnsupportedOperationException("MediaPlayer does not support "
+ + "setSurface() on this version of the platform.");
+ }
+ } else if (mSurfaceHolder != null) {
+ mMediaPlayer.setDisplay(mSurfaceHolder);
+ } else {
+ mMediaPlayer.setDisplay(null);
+ }
+ }
+
+ protected abstract void updateSize();
+
+ private void reset() {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+ mMediaPlayer = new MediaPlayer();
+ mMediaPlayer.setOnPreparedListener(this);
+ mMediaPlayer.setOnCompletionListener(this);
+ mMediaPlayer.setOnErrorListener(this);
+ mMediaPlayer.setOnSeekCompleteListener(this);
+ updateSurface();
+ mState = STATE_IDLE;
+ mSeekToPos = 0;
+ }
+
+ private void updateVideoRect() {
+ if (mState != STATE_IDLE && mState != STATE_PLAY_PENDING) {
+ int width = mMediaPlayer.getVideoWidth();
+ int height = mMediaPlayer.getVideoHeight();
+ if (width > 0 && height > 0) {
+ mVideoWidth = width;
+ mVideoHeight = height;
+ updateSize();
+ } else {
+ Log.e(TAG, "video rect is 0x0!");
+ mVideoWidth = mVideoHeight = 0;
+ }
+ }
+ }
+
+ private static final class ICSMediaPlayer {
+ public static final void setSurface(MediaPlayer player, Surface surface) {
+ player.setSurface(surface);
+ }
+ }
+
+ /**
+ * Handles playback of a single media item using MediaPlayer in SurfaceView
+ */
+ public static class SurfaceViewPlayer extends LocalPlayer implements
+ SurfaceHolder.Callback {
+ private static final String TAG = "SurfaceViewPlayer";
+ private RouteInfo mRoute;
+ private final SurfaceView mSurfaceView;
+ private final FrameLayout mLayout;
+ private DemoPresentation mPresentation;
+
+ public SurfaceViewPlayer(Context context) {
+ super(context);
+
+ mLayout = (FrameLayout)((Activity)context).findViewById(R.id.player);
+ mSurfaceView = (SurfaceView)((Activity)context).findViewById(R.id.surface_view);
+
+ // add surface holder callback
+ SurfaceHolder holder = mSurfaceView.getHolder();
+ holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+ holder.addCallback(this);
+ }
+
+ @Override
+ public void connect(RouteInfo route) {
+ super.connect(route);
+ mRoute = route;
+ }
+
+ @Override
+ public void release() {
+ super.release();
+
+ // dismiss presentation display
+ if (mPresentation != null) {
+ Log.i(TAG, "Dismissing presentation because the activity is no longer visible.");
+ mPresentation.dismiss();
+ mPresentation = null;
+ }
+
+ // remove surface holder callback
+ SurfaceHolder holder = mSurfaceView.getHolder();
+ holder.removeCallback(this);
+
+ // hide the surface view when SurfaceViewPlayer is destroyed
+ mSurfaceView.setVisibility(View.GONE);
+ mLayout.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void updatePresentation() {
+ // Get the current route and its presentation display.
+ Display presentationDisplay = mRoute != null ? mRoute.getPresentationDisplay() : null;
+
+ // Dismiss the current presentation if the display has changed.
+ if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) {
+ Log.i(TAG, "Dismissing presentation because the current route no longer "
+ + "has a presentation display.");
+ mPresentation.dismiss();
+ mPresentation = null;
+ }
+
+ // Show a new presentation if needed.
+ if (mPresentation == null && presentationDisplay != null) {
+ Log.i(TAG, "Showing presentation on display: " + presentationDisplay);
+ mPresentation = new DemoPresentation(getContext(), presentationDisplay);
+ mPresentation.setOnDismissListener(mOnDismissListener);
+ try {
+ mPresentation.show();
+ } catch (WindowManager.InvalidDisplayException ex) {
+ Log.w(TAG, "Couldn't show presentation! Display was removed in "
+ + "the meantime.", ex);
+ mPresentation = null;
+ }
+ }
+
+ updateContents();
+ }
+
+ // SurfaceHolder.Callback
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format,
+ int width, int height) {
+ if (DEBUG) {
+ Log.d(TAG, "surfaceChanged: " + width + "x" + height);
+ }
+ setSurface(holder);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ if (DEBUG) {
+ Log.d(TAG, "surfaceCreated");
+ }
+ setSurface(holder);
+ updateSize();
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ if (DEBUG) {
+ Log.d(TAG, "surfaceDestroyed");
+ }
+ removeSurface(holder);
+ }
+
+ @Override
+ protected void updateSize() {
+ int width = getVideoWidth();
+ int height = getVideoHeight();
+ if (width > 0 && height > 0) {
+ if (mPresentation == null) {
+ int surfaceWidth = mLayout.getWidth();
+ int surfaceHeight = mLayout.getHeight();
+
+ // Calculate the new size of mSurfaceView, so that video is centered
+ // inside the framelayout with proper letterboxing/pillarboxing
+ ViewGroup.LayoutParams lp = mSurfaceView.getLayoutParams();
+ if (surfaceWidth * height < surfaceHeight * width) {
+ // Black bars on top&bottom, mSurfaceView has full layout width,
+ // while height is derived from video's aspect ratio
+ lp.width = surfaceWidth;
+ lp.height = surfaceWidth * height / width;
+ } else {
+ // Black bars on left&right, mSurfaceView has full layout height,
+ // while width is derived from video's aspect ratio
+ lp.width = surfaceHeight * width / height;
+ lp.height = surfaceHeight;
+ }
+ Log.i(TAG, "video rect is " + lp.width + "x" + lp.height);
+ mSurfaceView.setLayoutParams(lp);
+ } else {
+ mPresentation.updateSize(width, height);
+ }
+ }
+ }
+
+ private void updateContents() {
+ // Show either the content in the main activity or the content in the presentation
+ if (mPresentation != null) {
+ mLayout.setVisibility(View.GONE);
+ mSurfaceView.setVisibility(View.GONE);
+ } else {
+ mLayout.setVisibility(View.VISIBLE);
+ mSurfaceView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ // Listens for when presentations are dismissed.
+ private final DialogInterface.OnDismissListener mOnDismissListener =
+ new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ if (dialog == mPresentation) {
+ Log.i(TAG, "Presentation dismissed.");
+ mPresentation = null;
+ updateContents();
+ }
+ }
+ };
+
+ // Presentation
+ private final class DemoPresentation extends Presentation {
+ private SurfaceView mPresentationSurfaceView;
+
+ public DemoPresentation(Context context, Display display) {
+ super(context, display);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // Be sure to call the super class.
+ super.onCreate(savedInstanceState);
+
+ // Inflate the layout.
+ setContentView(R.layout.sample_media_router_presentation);
+
+ // Set up the surface view.
+ mPresentationSurfaceView = (SurfaceView)findViewById(R.id.surface_view);
+ SurfaceHolder holder = mPresentationSurfaceView.getHolder();
+ holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+ holder.addCallback(SurfaceViewPlayer.this);
+ Log.i(TAG, "Presentation created");
+ }
+
+ public void updateSize(int width, int height) {
+ int surfaceHeight = getWindow().getDecorView().getHeight();
+ int surfaceWidth = getWindow().getDecorView().getWidth();
+ ViewGroup.LayoutParams lp = mPresentationSurfaceView.getLayoutParams();
+ if (surfaceWidth * height < surfaceHeight * width) {
+ lp.width = surfaceWidth;
+ lp.height = surfaceWidth * height / width;
+ } else {
+ lp.width = surfaceHeight * width / height;
+ lp.height = surfaceHeight;
+ }
+ Log.i(TAG, "Presentation video rect is " + lp.width + "x" + lp.height);
+ mPresentationSurfaceView.setLayoutParams(lp);
+ }
+ }
+ }
+
+ /**
+ * Handles playback of a single media item using MediaPlayer in
+ * OverlayDisplayWindow.
+ */
+ public static class OverlayPlayer extends LocalPlayer implements
+ OverlayDisplayWindow.OverlayWindowListener {
+ private static final String TAG = "OverlayPlayer";
+ private final OverlayDisplayWindow mOverlay;
+
+ public OverlayPlayer(Context context) {
+ super(context);
+
+ mOverlay = OverlayDisplayWindow.create(getContext(),
+ getContext().getResources().getString(
+ R.string.sample_media_route_provider_remote),
+ 1024, 768, Gravity.CENTER);
+
+ mOverlay.setOverlayWindowListener(this);
+ }
+
+ @Override
+ public void connect(RouteInfo route) {
+ super.connect(route);
+ mOverlay.show();
+ }
+
+ @Override
+ public void release() {
+ super.release();
+ mOverlay.dismiss();
+ }
+
+ @Override
+ protected void updateSize() {
+ int width = getVideoWidth();
+ int height = getVideoHeight();
+ if (width > 0 && height > 0) {
+ mOverlay.updateAspectRatio(width, height);
+ }
+ }
+
+ // OverlayDisplayWindow.OverlayWindowListener
+ @Override
+ public void onWindowCreated(Surface surface) {
+ setSurface(surface);
+ }
+
+ @Override
+ public void onWindowCreated(SurfaceHolder surfaceHolder) {
+ setSurface(surfaceHolder);
+ }
+
+ @Override
+ public void onWindowDestroyed() {
+ setSurface((SurfaceHolder)null);
+ }
+ }
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/MainActivity.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/MainActivity.java
new file mode 100644
index 0000000..ad283dd
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/MainActivity.java
@@ -0,0 +1,724 @@
+/*
+ * 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 com.example.android.mediarouter.player;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.media.RemoteControlClient;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.app.MediaRouteActionProvider;
+import android.support.v7.app.MediaRouteDiscoveryFragment;
+import android.support.v7.media.MediaControlIntent;
+import android.support.v7.media.MediaItemStatus;
+import android.support.v7.media.MediaRouteSelector;
+import android.support.v7.media.MediaRouter;
+import android.support.v7.media.MediaRouter.Callback;
+import android.support.v7.media.MediaRouter.ProviderInfo;
+import android.support.v7.media.MediaRouter.RouteInfo;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.ImageButton;
+import android.widget.ListView;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TabHost;
+import android.widget.TabHost.OnTabChangeListener;
+import android.widget.TabHost.TabSpec;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.example.android.mediarouter.R;
+import com.example.android.mediarouter.provider.SampleMediaRouteProvider;
+
+import java.io.File;
+
+/**
+ * <h3>Media Router Support Activity</h3>
+ * <p/>
+ * <p>
+ * This demonstrates how to use the {@link MediaRouter} API to build an
+ * application that allows the user to send content to various rendering
+ * targets.
+ * </p>
+ */
+public class MainActivity extends ActionBarActivity {
+ private static final String TAG = "MainActivity";
+ private static final String DISCOVERY_FRAGMENT_TAG = "DiscoveryFragment";
+
+ private MediaRouter mMediaRouter;
+ private MediaRouteSelector mSelector;
+ private LibraryAdapter mLibraryItems;
+ private PlaylistAdapter mPlayListItems;
+ private TextView mInfoTextView;
+ private ListView mLibraryView;
+ private ListView mPlayListView;
+ private ImageButton mPauseResumeButton;
+ private ImageButton mStopButton;
+ private SeekBar mSeekBar;
+ private boolean mPaused;
+ private boolean mNeedResume;
+ private boolean mSeeking;
+
+ private RemoteControlClient mRemoteControlClient;
+ private ComponentName mEventReceiver;
+ private AudioManager mAudioManager;
+ private PendingIntent mMediaPendingIntent;
+
+ private final Handler mHandler = new Handler();
+ private final Runnable mUpdateSeekRunnable = new Runnable() {
+ @Override
+ public void run() {
+ updateProgress();
+ // update UI every 1 second
+ mHandler.postDelayed(this, 1000);
+ }
+ };
+
+ private final SessionManager mSessionManager = new SessionManager("app");
+ private Player mPlayer;
+
+ private final MediaRouter.Callback mMediaRouterCB = new MediaRouter.Callback() {
+ // Return a custom callback that will simply log all of the route events
+ // for demonstration purposes.
+ @Override
+ public void onRouteAdded(MediaRouter router, RouteInfo route) {
+ Log.d(TAG, "onRouteAdded: route=" + route);
+ }
+
+ @Override
+ public void onRouteChanged(MediaRouter router, RouteInfo route) {
+ Log.d(TAG, "onRouteChanged: route=" + route);
+ }
+
+ @Override
+ public void onRouteRemoved(MediaRouter router, RouteInfo route) {
+ Log.d(TAG, "onRouteRemoved: route=" + route);
+ }
+
+ @Override
+ public void onRouteSelected(MediaRouter router, RouteInfo route) {
+ Log.d(TAG, "onRouteSelected: route=" + route);
+
+ mPlayer = Player.create(MainActivity.this, route);
+ mPlayer.updatePresentation();
+ mSessionManager.setPlayer(mPlayer);
+ mSessionManager.unsuspend();
+
+ registerRemoteControlClient();
+ updateUi();
+ }
+
+ @Override
+ public void onRouteUnselected(MediaRouter router, RouteInfo route) {
+ Log.d(TAG, "onRouteUnselected: route=" + route);
+ unregisterRemoteControlClient();
+
+ PlaylistItem item = getCheckedPlaylistItem();
+ if (item != null) {
+ long pos = item.getPosition() +
+ (mPaused ? 0 : (SystemClock.elapsedRealtime() - item.getTimestamp()));
+ mSessionManager.suspend(pos);
+ }
+ mPlayer.updatePresentation();
+ mPlayer.release();
+ }
+
+ @Override
+ public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
+ Log.d(TAG, "onRouteVolumeChanged: route=" + route);
+ }
+
+ @Override
+ public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
+ Log.d(TAG, "onRoutePresentationDisplayChanged: route=" + route);
+ mPlayer.updatePresentation();
+ }
+
+ @Override
+ public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
+ Log.d(TAG, "onRouteProviderAdded: provider=" + provider);
+ }
+
+ @Override
+ public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
+ Log.d(TAG, "onRouteProviderRemoved: provider=" + provider);
+ }
+
+ @Override
+ public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
+ Log.d(TAG, "onRouteProviderChanged: provider=" + provider);
+ }
+ };
+
+ private final OnAudioFocusChangeListener mAfChangeListener = new OnAudioFocusChangeListener() {
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
+ Log.d(TAG, "onAudioFocusChange: LOSS_TRANSIENT");
+ } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+ Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_GAIN");
+ } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
+ Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_LOSS");
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // Be sure to call the super class.
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ mPlayer = (Player) savedInstanceState.getSerializable("mPlayer");
+ }
+
+ // Get the media router service.
+ mMediaRouter = MediaRouter.getInstance(this);
+
+ // Create a route selector for the type of routes that we care about.
+ mSelector =
+ new MediaRouteSelector.Builder().addControlCategory(MediaControlIntent
+ .CATEGORY_LIVE_AUDIO).addControlCategory(MediaControlIntent
+ .CATEGORY_LIVE_VIDEO).addControlCategory(MediaControlIntent
+ .CATEGORY_REMOTE_PLAYBACK).addControlCategory(SampleMediaRouteProvider
+ .CATEGORY_SAMPLE_ROUTE).build();
+
+ // Add a fragment to take care of media route discovery.
+ // This fragment automatically adds or removes a callback whenever the activity
+ // is started or stopped.
+ FragmentManager fm = getSupportFragmentManager();
+ DiscoveryFragment fragment =
+ (DiscoveryFragment) fm.findFragmentByTag(DISCOVERY_FRAGMENT_TAG);
+ if (fragment == null) {
+ fragment = new DiscoveryFragment(mMediaRouterCB);
+ fragment.setRouteSelector(mSelector);
+ fm.beginTransaction().add(fragment, DISCOVERY_FRAGMENT_TAG).commit();
+ } else {
+ fragment.setCallback(mMediaRouterCB);
+ fragment.setRouteSelector(mSelector);
+ }
+
+ // Populate an array adapter with streaming media items.
+ String[] mediaNames = getResources().getStringArray(R.array.media_names);
+ String[] mediaUris = getResources().getStringArray(R.array.media_uris);
+ mLibraryItems = new LibraryAdapter();
+ for (int i = 0; i < mediaNames.length; i++) {
+ mLibraryItems.add(new MediaItem(
+ "[streaming] " + mediaNames[i], Uri.parse(mediaUris[i]), "video/mp4"));
+ }
+
+ // Scan local external storage directory for media files.
+ File externalDir = Environment.getExternalStorageDirectory();
+ if (externalDir != null) {
+ File list[] = externalDir.listFiles();
+ if (list != null) {
+ for (int i = 0; i < list.length; i++) {
+ String filename = list[i].getName();
+ if (filename.matches(".*\\.(m4v|mp4)")) {
+ mLibraryItems.add(new MediaItem(
+ "[local] " + filename, Uri.fromFile(list[i]), "video/mp4"));
+ }
+ }
+ }
+ }
+
+ mPlayListItems = new PlaylistAdapter();
+
+ // Initialize the layout.
+ setContentView(R.layout.sample_media_router);
+
+ TabHost tabHost = (TabHost) findViewById(R.id.tabHost);
+ tabHost.setup();
+ String tabName = getResources().getString(R.string.library_tab_text);
+ TabSpec spec1 = tabHost.newTabSpec(tabName);
+ spec1.setContent(R.id.tab1);
+ spec1.setIndicator(tabName);
+
+ tabName = getResources().getString(R.string.playlist_tab_text);
+ TabSpec spec2 = tabHost.newTabSpec(tabName);
+ spec2.setIndicator(tabName);
+ spec2.setContent(R.id.tab2);
+
+ tabName = getResources().getString(R.string.statistics_tab_text);
+ TabSpec spec3 = tabHost.newTabSpec(tabName);
+ spec3.setIndicator(tabName);
+ spec3.setContent(R.id.tab3);
+
+ tabHost.addTab(spec1);
+ tabHost.addTab(spec2);
+ tabHost.addTab(spec3);
+ tabHost.setOnTabChangedListener(new OnTabChangeListener() {
+ @Override
+ public void onTabChanged(String arg0) {
+ updateUi();
+ }
+ });
+
+ mLibraryView = (ListView) findViewById(R.id.media);
+ mLibraryView.setAdapter(mLibraryItems);
+ mLibraryView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ mLibraryView.setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ updateButtons();
+ }
+ });
+
+ mPlayListView = (ListView) findViewById(R.id.playlist);
+ mPlayListView.setAdapter(mPlayListItems);
+ mPlayListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ mPlayListView.setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ updateButtons();
+ }
+ });
+
+ mInfoTextView = (TextView) findViewById(R.id.info);
+
+ mPauseResumeButton = (ImageButton) findViewById(R.id.pause_resume_button);
+ mPauseResumeButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mPaused = !mPaused;
+ if (mPaused) {
+ mSessionManager.pause();
+ } else {
+ mSessionManager.resume();
+ }
+ }
+ });
+
+ mStopButton = (ImageButton) findViewById(R.id.stop_button);
+ mStopButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mPaused = false;
+ mSessionManager.stop();
+ }
+ });
+
+ mSeekBar = (SeekBar) findViewById(R.id.seekbar);
+ mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ PlaylistItem item = getCheckedPlaylistItem();
+ if (fromUser && item != null && item.getDuration() > 0) {
+ long pos = progress * item.getDuration() / 100;
+ mSessionManager.seek(item.getItemId(), pos);
+ item.setPosition(pos);
+ item.setTimestamp(SystemClock.elapsedRealtime());
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ mSeeking = true;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ mSeeking = false;
+ updateUi();
+ }
+ });
+
+ // Schedule Ui update
+ mHandler.postDelayed(mUpdateSeekRunnable, 1000);
+
+ // Build the PendingIntent for the remote control client
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ mEventReceiver =
+ new ComponentName(getPackageName(), SampleMediaButtonReceiver.class.getName());
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ mediaButtonIntent.setComponent(mEventReceiver);
+ mMediaPendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
+
+ // Create and register the remote control client
+ registerRemoteControlClient();
+
+ // Set up playback manager and player
+ mPlayer = Player.create(MainActivity.this, mMediaRouter.getSelectedRoute());
+ mSessionManager.setPlayer(mPlayer);
+ mSessionManager.setCallback(new SessionManager.Callback() {
+ @Override
+ public void onStatusChanged() {
+ updateUi();
+ }
+
+ @Override
+ public void onItemChanged(PlaylistItem item) {
+ }
+ });
+
+ updateUi();
+ }
+
+ private void registerRemoteControlClient() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ // Create the RCC and register with AudioManager and MediaRouter
+ mAudioManager.requestAudioFocus(mAfChangeListener, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN);
+ mAudioManager.registerMediaButtonEventReceiver(mEventReceiver);
+ mRemoteControlClient = new RemoteControlClient(mMediaPendingIntent);
+ mAudioManager.registerRemoteControlClient(mRemoteControlClient);
+ mMediaRouter.addRemoteControlClient(mRemoteControlClient);
+ SampleMediaButtonReceiver.setActivity(MainActivity.this);
+ mRemoteControlClient.setTransportControlFlags(RemoteControlClient
+ .FLAG_KEY_MEDIA_PLAY_PAUSE);
+ mRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
+ }
+ }
+
+ private void unregisterRemoteControlClient() {
+ // Unregister the RCC with AudioManager and MediaRouter
+ if (mRemoteControlClient != null) {
+ mRemoteControlClient.setTransportControlFlags(0);
+ mAudioManager.abandonAudioFocus(mAfChangeListener);
+ mAudioManager.unregisterMediaButtonEventReceiver(mEventReceiver);
+ mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
+ mMediaRouter.removeRemoteControlClient(mRemoteControlClient);
+ SampleMediaButtonReceiver.setActivity(null);
+ mRemoteControlClient = null;
+ }
+ }
+
+ public boolean handleMediaKey(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: {
+ Log.d(TAG, "Received Play/Pause event from RemoteControlClient");
+ mPaused = !mPaused;
+ if (mPaused) {
+ mSessionManager.pause();
+ } else {
+ mSessionManager.resume();
+ }
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_PLAY: {
+ Log.d(TAG, "Received Play event from RemoteControlClient");
+ if (mPaused) {
+ mPaused = false;
+ mSessionManager.resume();
+ }
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_PAUSE: {
+ Log.d(TAG, "Received Pause event from RemoteControlClient");
+ if (!mPaused) {
+ mPaused = true;
+ mSessionManager.pause();
+ }
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_STOP: {
+ Log.d(TAG, "Received Stop event from RemoteControlClient");
+ mPaused = false;
+ mSessionManager.stop();
+ return true;
+ }
+ default:
+ break;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return handleMediaKey(event) || super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return handleMediaKey(event) || super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public void onStart() {
+ // Be sure to call the super class.
+ super.onStart();
+ }
+
+ @Override
+ public void onPause() {
+ // pause media player for local playback case only
+ if (!mPlayer.isRemotePlayback() && !mPaused) {
+ mNeedResume = true;
+ mSessionManager.pause();
+ }
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ // resume media player for local playback case only
+ if (!mPlayer.isRemotePlayback() && mNeedResume) {
+ mSessionManager.resume();
+ mNeedResume = false;
+ }
+ super.onResume();
+ }
+
+ @Override
+ public void onDestroy() {
+ // Unregister the remote control client
+ unregisterRemoteControlClient();
+
+ mPaused = false;
+ mSessionManager.stop();
+ mPlayer.release();
+ super.onDestroy();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Be sure to call the super class.
+ super.onCreateOptionsMenu(menu);
+
+ // Inflate the menu and configure the media router action provider.
+ getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);
+
+ MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
+ MediaRouteActionProvider mediaRouteActionProvider =
+ (MediaRouteActionProvider) MenuItemCompat.getActionProvider(mediaRouteMenuItem);
+ mediaRouteActionProvider.setRouteSelector(mSelector);
+
+ // Return true to show the menu.
+ return true;
+ }
+
+ private void updateProgress() {
+ // Estimate content position from last status time and elapsed time.
+ // (Note this might be slightly out of sync with remote side, however
+ // it avoids frequent polling the MRP.)
+ int progress = 0;
+ PlaylistItem item = getCheckedPlaylistItem();
+ if (item != null) {
+ int state = item.getState();
+ long duration = item.getDuration();
+ if (duration <= 0) {
+ if (state == MediaItemStatus.PLAYBACK_STATE_PLAYING ||
+ state == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+ mSessionManager.updateStatus();
+ }
+ } else {
+ long position = item.getPosition();
+ long timeDelta =
+ mPaused ? 0 : (SystemClock.elapsedRealtime() - item.getTimestamp());
+ progress = (int) (100.0 * (position + timeDelta) / duration);
+ }
+ }
+ mSeekBar.setProgress(progress);
+ }
+
+ private void updateUi() {
+ updatePlaylist();
+ updateRouteDescription();
+ updateButtons();
+ }
+
+ private void updatePlaylist() {
+ mPlayListItems.clear();
+ for (PlaylistItem item : mSessionManager.getPlaylist()) {
+ mPlayListItems.add(item);
+ }
+ mPlayListView.invalidate();
+ }
+
+
+ private void updateRouteDescription() {
+ RouteInfo route = mMediaRouter.getSelectedRoute();
+ mInfoTextView.setText(
+ "Currently selected route:" + "\nName: " + route.getName() + "\nProvider: " +
+ route.getProvider().getPackageName() + "\nDescription: " +
+ route.getDescription() + "\nStatistics: " +
+ mSessionManager.getStatistics());
+ }
+
+ private void updateButtons() {
+ MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
+ // show pause or resume icon depending on current state
+ mPauseResumeButton.setImageResource(
+ mPaused ? R.drawable.ic_action_play : R.drawable.ic_action_pause);
+ // disable pause/resume/stop if no session
+ mPauseResumeButton.setEnabled(mSessionManager.hasSession());
+ mStopButton.setEnabled(mSessionManager.hasSession());
+ // only enable seek bar when duration is known
+ PlaylistItem item = getCheckedPlaylistItem();
+ mSeekBar.setEnabled(item != null && item.getDuration() > 0);
+ if (mRemoteControlClient != null) {
+ mRemoteControlClient.setPlaybackState(mPaused ? RemoteControlClient.PLAYSTATE_PAUSED :
+ RemoteControlClient.PLAYSTATE_PLAYING);
+ }
+ }
+
+ private PlaylistItem getCheckedPlaylistItem() {
+ int count = mPlayListView.getCount();
+ int index = mPlayListView.getCheckedItemPosition();
+ if (count > 0) {
+ if (index < 0 || index >= count) {
+ index = 0;
+ mPlayListView.setItemChecked(0, true);
+ }
+ return mPlayListItems.getItem(index);
+ }
+ return null;
+ }
+
+ public static final class DiscoveryFragment extends MediaRouteDiscoveryFragment {
+ private static final String TAG = "DiscoveryFragment";
+ private Callback mCallback;
+
+ public DiscoveryFragment() {
+ mCallback = null;
+ }
+
+ public DiscoveryFragment(Callback cb) {
+ mCallback = cb;
+ }
+
+ public void setCallback(Callback cb) {
+ mCallback = cb;
+ }
+
+ @Override
+ public Callback onCreateCallback() {
+ return mCallback;
+ }
+
+ @Override
+ public int onPrepareCallbackFlags() {
+ // Add the CALLBACK_FLAG_UNFILTERED_EVENTS flag to ensure that we will
+ // observe and log all route events including those that are for routes
+ // that do not match our selector. This is only for demonstration purposes
+ // and should not be needed by most applications.
+ return super.onPrepareCallbackFlags() | MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS;
+ }
+ }
+
+ private static final class MediaItem {
+ public final String mName;
+ public final Uri mUri;
+ public final String mMime;
+
+ public MediaItem(String name, Uri uri, String mime) {
+ mName = name;
+ mUri = uri;
+ mMime = mime;
+ }
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+ }
+
+ private final class LibraryAdapter extends ArrayAdapter<MediaItem> {
+ public LibraryAdapter() {
+ super(MainActivity.this, R.layout.media_item);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final View v;
+ if (convertView == null) {
+ v = getLayoutInflater().inflate(R.layout.media_item, null);
+ } else {
+ v = convertView;
+ }
+
+ final MediaItem item = getItem(position);
+
+ TextView tv = (TextView) v.findViewById(R.id.item_text);
+ tv.setText(item.mName);
+
+ ImageButton b = (ImageButton) v.findViewById(R.id.item_action);
+ b.setImageResource(R.drawable.ic_suggestions_add);
+ b.setTag(item);
+ b.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (item != null) {
+ mSessionManager.add(item.mUri, item.mMime);
+ Toast.makeText(MainActivity.this, R.string.playlist_item_added_text,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+
+ return v;
+ }
+ }
+
+ private final class PlaylistAdapter extends ArrayAdapter<PlaylistItem> {
+ public PlaylistAdapter() {
+ super(MainActivity.this, R.layout.media_item);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final View v;
+ if (convertView == null) {
+ v = getLayoutInflater().inflate(R.layout.media_item, null);
+ } else {
+ v = convertView;
+ }
+
+ final PlaylistItem item = getItem(position);
+
+ TextView tv = (TextView) v.findViewById(R.id.item_text);
+ tv.setText(item.toString());
+
+ ImageButton b = (ImageButton) v.findViewById(R.id.item_action);
+ b.setImageResource(R.drawable.ic_suggestions_delete);
+ b.setTag(item);
+ b.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (item != null) {
+ mSessionManager.remove(item.getItemId());
+ Toast.makeText(MainActivity.this, R.string.playlist_item_removed_text,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+
+ return v;
+ }
+ }
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/OverlayDisplayWindow.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/OverlayDisplayWindow.java
new file mode 100644
index 0000000..5834830
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/OverlayDisplayWindow.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2012 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 com.example.android.mediarouter.player;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.hardware.display.DisplayManager;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.example.android.mediarouter.R;
+
+/**
+ * Manages an overlay display window, used for simulating remote playback.
+ */
+public abstract class OverlayDisplayWindow {
+ private static final String TAG = "OverlayDisplayWindow";
+ private static final boolean DEBUG = false;
+
+ private static final float WINDOW_ALPHA = 0.8f;
+ private static final float INITIAL_SCALE = 0.5f;
+ private static final float MIN_SCALE = 0.3f;
+ private static final float MAX_SCALE = 1.0f;
+
+ protected final Context mContext;
+ protected final String mName;
+ protected final int mWidth;
+ protected final int mHeight;
+ protected final int mGravity;
+ protected OverlayWindowListener mListener;
+
+ protected OverlayDisplayWindow(Context context, String name,
+ int width, int height, int gravity) {
+ mContext = context;
+ mName = name;
+ mWidth = width;
+ mHeight = height;
+ mGravity = gravity;
+ }
+
+ public static OverlayDisplayWindow create(Context context, String name,
+ int width, int height, int gravity) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return new JellybeanMr1Impl(context, name, width, height, gravity);
+ } else {
+ return new LegacyImpl(context, name, width, height, gravity);
+ }
+ }
+
+ public void setOverlayWindowListener(OverlayWindowListener listener) {
+ mListener = listener;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public abstract void show();
+
+ public abstract void dismiss();
+
+ public abstract void updateAspectRatio(int width, int height);
+
+ // Watches for significant changes in the overlay display window lifecycle.
+ public interface OverlayWindowListener {
+ public void onWindowCreated(Surface surface);
+ public void onWindowCreated(SurfaceHolder surfaceHolder);
+ public void onWindowDestroyed();
+ }
+
+ /**
+ * Implementation for older versions.
+ */
+ private static final class LegacyImpl extends OverlayDisplayWindow {
+ private final WindowManager mWindowManager;
+
+ private boolean mWindowVisible;
+ private SurfaceView mSurfaceView;
+
+ public LegacyImpl(Context context, String name,
+ int width, int height, int gravity) {
+ super(context, name, width, height, gravity);
+
+ mWindowManager = (WindowManager)context.getSystemService(
+ Context.WINDOW_SERVICE);
+ }
+
+ @Override
+ public void show() {
+ if (!mWindowVisible) {
+ mSurfaceView = new SurfaceView(mContext);
+
+ Display display = mWindowManager.getDefaultDisplay();
+
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ params.alpha = WINDOW_ALPHA;
+ params.gravity = Gravity.LEFT | Gravity.BOTTOM;
+ params.setTitle(mName);
+
+ int width = (int)(display.getWidth() * INITIAL_SCALE);
+ int height = (int)(display.getHeight() * INITIAL_SCALE);
+ if (mWidth > mHeight) {
+ height = mHeight * width / mWidth;
+ } else {
+ width = mWidth * height / mHeight;
+ }
+ params.width = width;
+ params.height = height;
+
+ mWindowManager.addView(mSurfaceView, params);
+ mWindowVisible = true;
+
+ SurfaceHolder holder = mSurfaceView.getHolder();
+ holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+ mListener.onWindowCreated(holder);
+ }
+ }
+
+ @Override
+ public void dismiss() {
+ if (mWindowVisible) {
+ mListener.onWindowDestroyed();
+
+ mWindowManager.removeView(mSurfaceView);
+ mWindowVisible = false;
+ }
+ }
+
+ @Override
+ public void updateAspectRatio(int width, int height) {
+ }
+ }
+
+ /**
+ * Implementation for API version 17+.
+ */
+ private static final class JellybeanMr1Impl extends OverlayDisplayWindow {
+ // When true, disables support for moving and resizing the overlay.
+ // The window is made non-touchable, which makes it possible to
+ // directly interact with the content underneath.
+ private static final boolean DISABLE_MOVE_AND_RESIZE = false;
+
+ private final DisplayManager mDisplayManager;
+ private final WindowManager mWindowManager;
+
+ private final Display mDefaultDisplay;
+ private final DisplayMetrics mDefaultDisplayMetrics = new DisplayMetrics();
+
+ private View mWindowContent;
+ private WindowManager.LayoutParams mWindowParams;
+ private TextureView mTextureView;
+ private TextView mNameTextView;
+
+ private GestureDetector mGestureDetector;
+ private ScaleGestureDetector mScaleGestureDetector;
+
+ private boolean mWindowVisible;
+ private int mWindowX;
+ private int mWindowY;
+ private float mWindowScale;
+
+ private float mLiveTranslationX;
+ private float mLiveTranslationY;
+ private float mLiveScale = 1.0f;
+
+ public JellybeanMr1Impl(Context context, String name,
+ int width, int height, int gravity) {
+ super(context, name, width, height, gravity);
+
+ mDisplayManager = (DisplayManager)context.getSystemService(
+ Context.DISPLAY_SERVICE);
+ mWindowManager = (WindowManager)context.getSystemService(
+ Context.WINDOW_SERVICE);
+
+ mDefaultDisplay = mWindowManager.getDefaultDisplay();
+ updateDefaultDisplayInfo();
+
+ createWindow();
+ }
+
+ @Override
+ public void show() {
+ if (!mWindowVisible) {
+ mDisplayManager.registerDisplayListener(mDisplayListener, null);
+ if (!updateDefaultDisplayInfo()) {
+ mDisplayManager.unregisterDisplayListener(mDisplayListener);
+ return;
+ }
+
+ clearLiveState();
+ updateWindowParams();
+ mWindowManager.addView(mWindowContent, mWindowParams);
+ mWindowVisible = true;
+ }
+ }
+
+ @Override
+ public void dismiss() {
+ if (mWindowVisible) {
+ mDisplayManager.unregisterDisplayListener(mDisplayListener);
+ mWindowManager.removeView(mWindowContent);
+ mWindowVisible = false;
+ }
+ }
+
+ @Override
+ public void updateAspectRatio(int width, int height) {
+ if (mWidth * height < mHeight * width) {
+ mTextureView.getLayoutParams().width = mWidth;
+ mTextureView.getLayoutParams().height = mWidth * height / width;
+ } else {
+ mTextureView.getLayoutParams().width = mHeight * width / height;
+ mTextureView.getLayoutParams().height = mHeight;
+ }
+ relayout();
+ }
+
+ private void relayout() {
+ if (mWindowVisible) {
+ updateWindowParams();
+ mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
+ }
+ }
+
+ private boolean updateDefaultDisplayInfo() {
+ mDefaultDisplay.getMetrics(mDefaultDisplayMetrics);
+ return true;
+ }
+
+ private void createWindow() {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+
+ mWindowContent = inflater.inflate(
+ R.layout.overlay_display_window, null);
+ mWindowContent.setOnTouchListener(mOnTouchListener);
+
+ mTextureView = (TextureView)mWindowContent.findViewById(
+ R.id.overlay_display_window_texture);
+ mTextureView.setPivotX(0);
+ mTextureView.setPivotY(0);
+ mTextureView.getLayoutParams().width = mWidth;
+ mTextureView.getLayoutParams().height = mHeight;
+ mTextureView.setOpaque(false);
+ mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
+
+ mNameTextView = (TextView)mWindowContent.findViewById(
+ R.id.overlay_display_window_title);
+ mNameTextView.setText(mName);
+
+ mWindowParams = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ if (DISABLE_MOVE_AND_RESIZE) {
+ mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ }
+ mWindowParams.alpha = WINDOW_ALPHA;
+ mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
+ mWindowParams.setTitle(mName);
+
+ mGestureDetector = new GestureDetector(mContext, mOnGestureListener);
+ mScaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener);
+
+ // Set the initial position and scale.
+ // The position and scale will be clamped when the display is first shown.
+ mWindowX = (mGravity & Gravity.LEFT) == Gravity.LEFT ?
+ 0 : mDefaultDisplayMetrics.widthPixels;
+ mWindowY = (mGravity & Gravity.TOP) == Gravity.TOP ?
+ 0 : mDefaultDisplayMetrics.heightPixels;
+ Log.d(TAG, mDefaultDisplayMetrics.toString());
+ mWindowScale = INITIAL_SCALE;
+
+ // calculate and save initial settings
+ updateWindowParams();
+ saveWindowParams();
+ }
+
+ private void updateWindowParams() {
+ float scale = mWindowScale * mLiveScale;
+ scale = Math.min(scale, (float)mDefaultDisplayMetrics.widthPixels / mWidth);
+ scale = Math.min(scale, (float)mDefaultDisplayMetrics.heightPixels / mHeight);
+ scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
+
+ float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f;
+ int width = (int)(mWidth * scale);
+ int height = (int)(mHeight * scale);
+ int x = (int)(mWindowX + mLiveTranslationX - width * offsetScale);
+ int y = (int)(mWindowY + mLiveTranslationY - height * offsetScale);
+ x = Math.max(0, Math.min(x, mDefaultDisplayMetrics.widthPixels - width));
+ y = Math.max(0, Math.min(y, mDefaultDisplayMetrics.heightPixels - height));
+
+ if (DEBUG) {
+ Log.d(TAG, "updateWindowParams: scale=" + scale
+ + ", offsetScale=" + offsetScale
+ + ", x=" + x + ", y=" + y
+ + ", width=" + width + ", height=" + height);
+ }
+
+ mTextureView.setScaleX(scale);
+ mTextureView.setScaleY(scale);
+
+ mTextureView.setTranslationX(
+ (mWidth - mTextureView.getLayoutParams().width) * scale / 2);
+ mTextureView.setTranslationY(
+ (mHeight - mTextureView.getLayoutParams().height) * scale / 2);
+
+ mWindowParams.x = x;
+ mWindowParams.y = y;
+ mWindowParams.width = width;
+ mWindowParams.height = height;
+ }
+
+ private void saveWindowParams() {
+ mWindowX = mWindowParams.x;
+ mWindowY = mWindowParams.y;
+ mWindowScale = mTextureView.getScaleX();
+ clearLiveState();
+ }
+
+ private void clearLiveState() {
+ mLiveTranslationX = 0f;
+ mLiveTranslationY = 0f;
+ mLiveScale = 1.0f;
+ }
+
+ private final DisplayManager.DisplayListener mDisplayListener =
+ new DisplayManager.DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId == mDefaultDisplay.getDisplayId()) {
+ if (updateDefaultDisplayInfo()) {
+ relayout();
+ } else {
+ dismiss();
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ if (displayId == mDefaultDisplay.getDisplayId()) {
+ dismiss();
+ }
+ }
+ };
+
+ private final SurfaceTextureListener mSurfaceTextureListener =
+ new SurfaceTextureListener() {
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
+ int width, int height) {
+ if (mListener != null) {
+ mListener.onWindowCreated(new Surface(surfaceTexture));
+ }
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+ if (mListener != null) {
+ mListener.onWindowDestroyed();
+ }
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
+ int width, int height) {
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
+ }
+ };
+
+ private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ // Work in screen coordinates.
+ final float oldX = event.getX();
+ final float oldY = event.getY();
+ event.setLocation(event.getRawX(), event.getRawY());
+
+ mGestureDetector.onTouchEvent(event);
+ mScaleGestureDetector.onTouchEvent(event);
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ saveWindowParams();
+ break;
+ }
+
+ // Revert to window coordinates.
+ event.setLocation(oldX, oldY);
+ return true;
+ }
+ };
+
+ private final GestureDetector.OnGestureListener mOnGestureListener =
+ new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2,
+ float distanceX, float distanceY) {
+ mLiveTranslationX -= distanceX;
+ mLiveTranslationY -= distanceY;
+ relayout();
+ return true;
+ }
+ };
+
+ private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener =
+ new ScaleGestureDetector.SimpleOnScaleGestureListener() {
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ mLiveScale *= detector.getScaleFactor();
+ relayout();
+ return true;
+ }
+ };
+ }
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/Player.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/Player.java
new file mode 100644
index 0000000..f842cf6
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/Player.java
@@ -0,0 +1,81 @@
+/*
+ * 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 com.example.android.mediarouter.player;
+
+import android.content.Context;
+import android.support.v7.media.MediaControlIntent;
+import android.support.v7.media.MediaRouter.RouteInfo;
+
+/**
+ * Abstraction of common playback operations of media items, such as play,
+ * seek, etc. Used by PlaybackManager as a backend to handle actual playback
+ * of media items.
+ */
+public abstract class Player {
+ protected Callback mCallback;
+
+ public abstract boolean isRemotePlayback();
+ public abstract boolean isQueuingSupported();
+
+ public abstract void connect(RouteInfo route);
+ public abstract void release();
+
+ // basic operations that are always supported
+ public abstract void play(final PlaylistItem item);
+ public abstract void seek(final PlaylistItem item);
+ public abstract void getStatus(final PlaylistItem item, final boolean update);
+ public abstract void pause();
+ public abstract void resume();
+ public abstract void stop();
+
+ // advanced queuing (enqueue & remove) are only supported
+ // if isQueuingSupported() returns true
+ public abstract void enqueue(final PlaylistItem item);
+ public abstract PlaylistItem remove(String iid);
+
+ // route statistics
+ public void updateStatistics() {}
+ public String getStatistics() { return ""; }
+
+ // presentation display
+ public void updatePresentation() {}
+
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ public static Player create(Context context, RouteInfo route) {
+ Player player;
+ if (route != null && route.supportsControlCategory(
+ MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
+ player = new RemotePlayer(context);
+ } else if (route != null) {
+ player = new LocalPlayer.SurfaceViewPlayer(context);
+ } else {
+ player = new LocalPlayer.OverlayPlayer(context);
+ }
+ player.connect(route);
+ return player;
+ }
+
+ public interface Callback {
+ void onError();
+ void onCompletion();
+ void onPlaylistChanged();
+ void onPlaylistReady();
+ }
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/PlaylistItem.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/PlaylistItem.java
new file mode 100644
index 0000000..a560538
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/PlaylistItem.java
@@ -0,0 +1,130 @@
+/*
+ * 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 com.example.android.mediarouter.player;
+
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.support.v7.media.MediaItemStatus;
+
+/**
+ * PlaylistItem helps keep track of the current status of an media item.
+ */
+public final class PlaylistItem {
+ // immutables
+ private final String mSessionId;
+ private final String mItemId;
+ private final Uri mUri;
+ private final String mMime;
+ private final PendingIntent mUpdateReceiver;
+ // changeable states
+ private int mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PENDING;
+ private long mContentPosition;
+ private long mContentDuration;
+ private long mTimestamp;
+ private String mRemoteItemId;
+
+ public PlaylistItem(String qid, String iid, Uri uri, String mime, PendingIntent pi) {
+ mSessionId = qid;
+ mItemId = iid;
+ mUri = uri;
+ mMime = mime;
+ mUpdateReceiver = pi;
+ setTimestamp(SystemClock.elapsedRealtime());
+ }
+
+ public void setRemoteItemId(String riid) {
+ mRemoteItemId = riid;
+ }
+
+ public void setState(int state) {
+ mPlaybackState = state;
+ }
+
+ public void setPosition(long pos) {
+ mContentPosition = pos;
+ }
+
+ public void setTimestamp(long ts) {
+ mTimestamp = ts;
+ }
+
+ public void setDuration(long duration) {
+ mContentDuration = duration;
+ }
+
+ public String getSessionId() {
+ return mSessionId;
+ }
+
+ public String getItemId() {
+ return mItemId;
+ }
+
+ public String getRemoteItemId() {
+ return mRemoteItemId;
+ }
+
+ public Uri getUri() {
+ return mUri;
+ }
+
+ public PendingIntent getUpdateReceiver() {
+ return mUpdateReceiver;
+ }
+
+ public int getState() {
+ return mPlaybackState;
+ }
+
+ public long getPosition() {
+ return mContentPosition;
+ }
+
+ public long getDuration() {
+ return mContentDuration;
+ }
+
+ public long getTimestamp() {
+ return mTimestamp;
+ }
+
+ public MediaItemStatus getStatus() {
+ return new MediaItemStatus.Builder(mPlaybackState)
+ .setContentPosition(mContentPosition)
+ .setContentDuration(mContentDuration)
+ .setTimestamp(mTimestamp)
+ .build();
+ }
+
+ @Override
+ public String toString() {
+ String state[] = {
+ "PENDING",
+ "PLAYING",
+ "PAUSED",
+ "BUFFERING",
+ "FINISHED",
+ "CANCELED",
+ "INVALIDATED",
+ "ERROR"
+ };
+ return "[" + mSessionId + "|" + mItemId + "|"
+ + (mRemoteItemId != null ? mRemoteItemId : "-") + "|"
+ + state[mPlaybackState] + "] " + mUri.toString();
+ }
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/RemotePlayer.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/RemotePlayer.java
new file mode 100644
index 0000000..6726718
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/RemotePlayer.java
@@ -0,0 +1,482 @@
+/*
+ * 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 com.example.android.mediarouter.player;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.media.MediaItemStatus;
+import android.support.v7.media.MediaRouter.ControlRequestCallback;
+import android.support.v7.media.MediaRouter.RouteInfo;
+import android.support.v7.media.MediaSessionStatus;
+import android.support.v7.media.RemotePlaybackClient;
+import android.support.v7.media.RemotePlaybackClient.ItemActionCallback;
+import android.support.v7.media.RemotePlaybackClient.SessionActionCallback;
+import android.support.v7.media.RemotePlaybackClient.StatusCallback;
+import android.util.Log;
+
+import com.example.android.mediarouter.player.Player;
+import com.example.android.mediarouter.player.PlaylistItem;
+import com.example.android.mediarouter.provider.SampleMediaRouteProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handles playback of media items using a remote route.
+ *
+ * This class is used as a backend by PlaybackManager to feed media items to
+ * the remote route. When the remote route doesn't support queuing, media items
+ * are fed one-at-a-time; otherwise media items are enqueued to the remote side.
+ */
+public class RemotePlayer extends Player {
+ private static final String TAG = "RemotePlayer";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private Context mContext;
+ private RouteInfo mRoute;
+ private boolean mEnqueuePending;
+ private String mStatsInfo = "";
+ private List<PlaylistItem> mTempQueue = new ArrayList<PlaylistItem>();
+
+ private RemotePlaybackClient mClient;
+ private StatusCallback mStatusCallback = new StatusCallback() {
+ @Override
+ public void onItemStatusChanged(Bundle data,
+ String sessionId, MediaSessionStatus sessionStatus,
+ String itemId, MediaItemStatus itemStatus) {
+ logStatus("onItemStatusChanged", sessionId, sessionStatus, itemId, itemStatus);
+ if (mCallback != null) {
+ if (itemStatus.getPlaybackState() ==
+ MediaItemStatus.PLAYBACK_STATE_FINISHED) {
+ mCallback.onCompletion();
+ } else if (itemStatus.getPlaybackState() ==
+ MediaItemStatus.PLAYBACK_STATE_ERROR) {
+ mCallback.onError();
+ }
+ }
+ }
+
+ @Override
+ public void onSessionStatusChanged(Bundle data,
+ String sessionId, MediaSessionStatus sessionStatus) {
+ logStatus("onSessionStatusChanged", sessionId, sessionStatus, null, null);
+ if (mCallback != null) {
+ mCallback.onPlaylistChanged();
+ }
+ }
+
+ @Override
+ public void onSessionChanged(String sessionId) {
+ if (DEBUG) {
+ Log.d(TAG, "onSessionChanged: sessionId=" + sessionId);
+ }
+ }
+ };
+
+ public RemotePlayer(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean isRemotePlayback() {
+ return true;
+ }
+
+ @Override
+ public boolean isQueuingSupported() {
+ return mClient.isQueuingSupported();
+ }
+
+ @Override
+ public void connect(RouteInfo route) {
+ mRoute = route;
+ mClient = new RemotePlaybackClient(mContext, route);
+ mClient.setStatusCallback(mStatusCallback);
+
+ if (DEBUG) {
+ Log.d(TAG, "connected to: " + route
+ + ", isRemotePlaybackSupported: " + mClient.isRemotePlaybackSupported()
+ + ", isQueuingSupported: "+ mClient.isQueuingSupported());
+ }
+ }
+
+ @Override
+ public void release() {
+ mClient.release();
+
+ if (DEBUG) {
+ Log.d(TAG, "released.");
+ }
+ }
+
+ // basic playback operations that are always supported
+ @Override
+ public void play(final PlaylistItem item) {
+ if (DEBUG) {
+ Log.d(TAG, "play: item=" + item);
+ }
+ mClient.play(item.getUri(), "video/mp4", null, 0, null, new ItemActionCallback() {
+ @Override
+ public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus,
+ String itemId, MediaItemStatus itemStatus) {
+ logStatus("play: succeeded", sessionId, sessionStatus, itemId, itemStatus);
+ item.setRemoteItemId(itemId);
+ if (item.getPosition() > 0) {
+ seekInternal(item);
+ }
+ if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+ pause();
+ }
+ if (mCallback != null) {
+ mCallback.onPlaylistChanged();
+ }
+ }
+
+ @Override
+ public void onError(String error, int code, Bundle data) {
+ logError("play: failed", error, code);
+ }
+ });
+ }
+
+ @Override
+ public void seek(final PlaylistItem item) {
+ seekInternal(item);
+ }
+
+ @Override
+ public void getStatus(final PlaylistItem item, final boolean update) {
+ if (!mClient.hasSession() || item.getRemoteItemId() == null) {
+ // if session is not valid or item id not assigend yet.
+ // just return, it's not fatal
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "getStatus: item=" + item + ", update=" + update);
+ }
+ mClient.getStatus(item.getRemoteItemId(), null, new ItemActionCallback() {
+ @Override
+ public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus,
+ String itemId, MediaItemStatus itemStatus) {
+ logStatus("getStatus: succeeded", sessionId, sessionStatus, itemId, itemStatus);
+ int state = itemStatus.getPlaybackState();
+ if (state == MediaItemStatus.PLAYBACK_STATE_PLAYING
+ || state == MediaItemStatus.PLAYBACK_STATE_PAUSED
+ || state == MediaItemStatus.PLAYBACK_STATE_PENDING) {
+ item.setState(state);
+ item.setPosition(itemStatus.getContentPosition());
+ item.setDuration(itemStatus.getContentDuration());
+ item.setTimestamp(itemStatus.getTimestamp());
+ }
+ if (update && mCallback != null) {
+ mCallback.onPlaylistReady();
+ }
+ }
+
+ @Override
+ public void onError(String error, int code, Bundle data) {
+ logError("getStatus: failed", error, code);
+ if (update && mCallback != null) {
+ mCallback.onPlaylistReady();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void pause() {
+ if (!mClient.hasSession()) {
+ // ignore if no session
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "pause");
+ }
+ mClient.pause(null, new SessionActionCallback() {
+ @Override
+ public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) {
+ logStatus("pause: succeeded", sessionId, sessionStatus, null, null);
+ if (mCallback != null) {
+ mCallback.onPlaylistChanged();
+ }
+ }
+
+ @Override
+ public void onError(String error, int code, Bundle data) {
+ logError("pause: failed", error, code);
+ }
+ });
+ }
+
+ @Override
+ public void resume() {
+ if (!mClient.hasSession()) {
+ // ignore if no session
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "resume");
+ }
+ mClient.resume(null, new SessionActionCallback() {
+ @Override
+ public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) {
+ logStatus("resume: succeeded", sessionId, sessionStatus, null, null);
+ if (mCallback != null) {
+ mCallback.onPlaylistChanged();
+ }
+ }
+
+ @Override
+ public void onError(String error, int code, Bundle data) {
+ logError("resume: failed", error, code);
+ }
+ });
+ }
+
+ @Override
+ public void stop() {
+ if (!mClient.hasSession()) {
+ // ignore if no session
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "stop");
+ }
+ mClient.stop(null, new SessionActionCallback() {
+ @Override
+ public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) {
+ logStatus("stop: succeeded", sessionId, sessionStatus, null, null);
+ if (mClient.isSessionManagementSupported()) {
+ endSession();
+ }
+ if (mCallback != null) {
+ mCallback.onPlaylistChanged();
+ }
+ }
+
+ @Override
+ public void onError(String error, int code, Bundle data) {
+ logError("stop: failed", error, code);
+ }
+ });
+ }
+
+ // enqueue & remove are only supported if isQueuingSupported() returns true
+ @Override
+ public void enqueue(final PlaylistItem item) {
+ throwIfQueuingUnsupported();
+
+ if (!mClient.hasSession() && !mEnqueuePending) {
+ mEnqueuePending = true;
+ if (mClient.isSessionManagementSupported()) {
+ startSession(item);
+ } else {
+ enqueueInternal(item);
+ }
+ } else if (mEnqueuePending){
+ mTempQueue.add(item);
+ } else {
+ enqueueInternal(item);
+ }
+ }
+
+ @Override
+ public PlaylistItem remove(String itemId) {
+ throwIfNoSession();
+ throwIfQueuingUnsupported();
+
+ if (DEBUG) {
+ Log.d(TAG, "remove: itemId=" + itemId);
+ }
+ mClient.remove(itemId, null, new ItemActionCallback() {
+ @Override
+ public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus,
+ String itemId, MediaItemStatus itemStatus) {
+ logStatus("remove: succeeded", sessionId, sessionStatus, itemId, itemStatus);
+ }
+
+ @Override
+ public void onError(String error, int code, Bundle data) {
+ logError("remove: failed", error, code);
+ }
+ });
+
+ return null;
+ }
+
+ @Override
+ public void updateStatistics() {
+ // clear stats info first
+ mStatsInfo = "";
+
+ Intent intent = new Intent(SampleMediaRouteProvider.ACTION_GET_STATISTICS);
+ intent.addCategory(SampleMediaRouteProvider.CATEGORY_SAMPLE_ROUTE);
+
+ if (mRoute != null && mRoute.supportsControlRequest(intent)) {
+ ControlRequestCallback callback = new ControlRequestCallback() {
+ @Override
+ public void onResult(Bundle data) {
+ if (DEBUG) {
+ Log.d(TAG, "getStatistics: succeeded: data=" + data);
+ }
+ if (data != null) {
+ int playbackCount = data.getInt(
+ SampleMediaRouteProvider.DATA_PLAYBACK_COUNT, -1);
+ mStatsInfo = "Total playback count: " + playbackCount;
+ }
+ }
+
+ @Override
+ public void onError(String error, Bundle data) {
+ Log.d(TAG, "getStatistics: failed: error=" + error + ", data=" + data);
+ }
+ };
+
+ mRoute.sendControlRequest(intent, callback);
+ }
+ }
+
+ @Override
+ public String getStatistics() {
+ return mStatsInfo;
+ }
+
+ private void enqueueInternal(final PlaylistItem item) {
+ throwIfQueuingUnsupported();
+
+ if (DEBUG) {
+ Log.d(TAG, "enqueue: item=" + item);
+ }
+ mClient.enqueue(item.getUri(), "video/mp4", null, 0, null, new ItemActionCallback() {
+ @Override
+ public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus,
+ String itemId, MediaItemStatus itemStatus) {
+ logStatus("enqueue: succeeded", sessionId, sessionStatus, itemId, itemStatus);
+ item.setRemoteItemId(itemId);
+ if (item.getPosition() > 0) {
+ seekInternal(item);
+ }
+ if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+ pause();
+ }
+ if (mEnqueuePending) {
+ mEnqueuePending = false;
+ for (PlaylistItem item : mTempQueue) {
+ enqueueInternal(item);
+ }
+ mTempQueue.clear();
+ }
+ if (mCallback != null) {
+ mCallback.onPlaylistChanged();
+ }
+ }
+
+ @Override
+ public void onError(String error, int code, Bundle data) {
+ logError("enqueue: failed", error, code);
+ if (mCallback != null) {
+ mCallback.onPlaylistChanged();
+ }
+ }
+ });
+ }
+
+ private void seekInternal(final PlaylistItem item) {
+ throwIfNoSession();
+
+ if (DEBUG) {
+ Log.d(TAG, "seek: item=" + item);
+ }
+ mClient.seek(item.getRemoteItemId(), item.getPosition(), null, new ItemActionCallback() {
+ @Override
+ public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus,
+ String itemId, MediaItemStatus itemStatus) {
+ logStatus("seek: succeeded", sessionId, sessionStatus, itemId, itemStatus);
+ if (mCallback != null) {
+ mCallback.onPlaylistChanged();
+ }
+ }
+
+ @Override
+ public void onError(String error, int code, Bundle data) {
+ logError("seek: failed", error, code);
+ }
+ });
+ }
+
+ private void startSession(final PlaylistItem item) {
+ mClient.startSession(null, new SessionActionCallback() {
+ @Override
+ public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) {
+ logStatus("startSession: succeeded", sessionId, sessionStatus, null, null);
+ enqueueInternal(item);
+ }
+
+ @Override
+ public void onError(String error, int code, Bundle data) {
+ logError("startSession: failed", error, code);
+ }
+ });
+ }
+
+ private void endSession() {
+ mClient.endSession(null, new SessionActionCallback() {
+ @Override
+ public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) {
+ logStatus("endSession: succeeded", sessionId, sessionStatus, null, null);
+ }
+
+ @Override
+ public void onError(String error, int code, Bundle data) {
+ logError("endSession: failed", error, code);
+ }
+ });
+ }
+
+ private void logStatus(String message,
+ String sessionId, MediaSessionStatus sessionStatus,
+ String itemId, MediaItemStatus itemStatus) {
+ if (DEBUG) {
+ String result = "";
+ if (sessionId != null && sessionStatus != null) {
+ result += "sessionId=" + sessionId + ", sessionStatus=" + sessionStatus;
+ }
+ if (itemId != null & itemStatus != null) {
+ result += (result.isEmpty() ? "" : ", ")
+ + "itemId=" + itemId + ", itemStatus=" + itemStatus;
+ }
+ Log.d(TAG, message + ": " + result);
+ }
+ }
+
+ private void logError(String message, String error, int code) {
+ Log.d(TAG, message + ": error=" + error + ", code=" + code);
+ }
+
+ private void throwIfNoSession() {
+ if (!mClient.hasSession()) {
+ throw new IllegalStateException("Session is invalid");
+ }
+ }
+
+ private void throwIfQueuingUnsupported() {
+ if (!isQueuingSupported()) {
+ throw new UnsupportedOperationException("Queuing is unsupported");
+ }
+ }
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/SampleMediaButtonReceiver.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/SampleMediaButtonReceiver.java
new file mode 100644
index 0000000..855bc1e
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/SampleMediaButtonReceiver.java
@@ -0,0 +1,46 @@
+/*
+ * 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 com.example.android.mediarouter.player;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.view.KeyEvent;
+
+/**
+ * Broadcast receiver for handling ACTION_MEDIA_BUTTON.
+ *
+ * This is needed to create the RemoteControlClient for controlling
+ * remote route volume in lock screen. It routes media key events back
+ * to main app activity MainActivity.
+ */
+public class SampleMediaButtonReceiver extends BroadcastReceiver {
+ private static final String TAG = "SampleMediaButtonReceiver";
+ private static MainActivity mActivity;
+
+ public static void setActivity(MainActivity activity) {
+ mActivity = activity;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mActivity != null && Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
+ mActivity.handleMediaKey(
+ (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT));
+ }
+ }
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/SessionManager.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/SessionManager.java
new file mode 100644
index 0000000..b6c5a46
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/SessionManager.java
@@ -0,0 +1,419 @@
+/*
+ * 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 com.example.android.mediarouter.player;
+
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.support.v7.media.MediaItemStatus;
+import android.support.v7.media.MediaSessionStatus;
+import android.util.Log;
+
+import com.example.android.mediarouter.player.Player.Callback;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * SessionManager manages a media session as a queue. It supports common
+ * queuing behaviors such as enqueue/remove of media items, pause/resume/stop,
+ * etc.
+ *
+ * Actual playback of a single media item is abstracted into a Player interface,
+ * and is handled outside this class.
+ */
+public class SessionManager implements Callback {
+ private static final String TAG = "SessionManager";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private String mName;
+ private int mSessionId;
+ private int mItemId;
+ private boolean mPaused;
+ private boolean mSessionValid;
+ private Player mPlayer;
+ private Callback mCallback;
+ private List<PlaylistItem> mPlaylist = new ArrayList<PlaylistItem>();
+
+ public SessionManager(String name) {
+ mName = name;
+ }
+
+ public boolean hasSession() {
+ return mSessionValid;
+ }
+
+ public String getSessionId() {
+ return mSessionValid ? Integer.toString(mSessionId) : null;
+ }
+
+ public PlaylistItem getCurrentItem() {
+ return mPlaylist.isEmpty() ? null : mPlaylist.get(0);
+ }
+
+ // Get the cached statistic info from the player (will not update it)
+ public String getStatistics() {
+ checkPlayer();
+ return mPlayer.getStatistics();
+ }
+
+ // Returns the cached playlist (note this is not responsible for updating it)
+ public List<PlaylistItem> getPlaylist() {
+ return mPlaylist;
+ }
+
+ // Updates the playlist asynchronously, calls onPlaylistReady() when finished.
+ public void updateStatus() {
+ if (DEBUG) {
+ log("updateStatus");
+ }
+ checkPlayer();
+ // update the statistics first, so that the stats string is valid when
+ // onPlaylistReady() gets called in the end
+ mPlayer.updateStatistics();
+
+ if (mPlaylist.isEmpty()) {
+ // If queue is empty, don't forget to call onPlaylistReady()!
+ onPlaylistReady();
+ } else if (mPlayer.isQueuingSupported()) {
+ // If player supports queuing, get status of each item. Player is
+ // responsible to call onPlaylistReady() after last getStatus().
+ // (update=1 requires player to callback onPlaylistReady())
+ for (int i = 0; i < mPlaylist.size(); i++) {
+ PlaylistItem item = mPlaylist.get(i);
+ mPlayer.getStatus(item, (i == mPlaylist.size() - 1) /* update */);
+ }
+ } else {
+ // Otherwise, only need to get status for current item. Player is
+ // responsible to call onPlaylistReady() when finished.
+ mPlayer.getStatus(getCurrentItem(), true /* update */);
+ }
+ }
+
+ public PlaylistItem add(Uri uri, String mime) {
+ return add(uri, mime, null);
+ }
+
+ public PlaylistItem add(Uri uri, String mime, PendingIntent receiver) {
+ if (DEBUG) {
+ log("add: uri=" + uri + ", receiver=" + receiver);
+ }
+ // create new session if needed
+ startSession();
+ checkPlayerAndSession();
+
+ // append new item with initial status PLAYBACK_STATE_PENDING
+ PlaylistItem item = new PlaylistItem(
+ Integer.toString(mSessionId), Integer.toString(mItemId), uri, mime, receiver);
+ mPlaylist.add(item);
+ mItemId++;
+
+ // if player supports queuing, enqueue the item now
+ if (mPlayer.isQueuingSupported()) {
+ mPlayer.enqueue(item);
+ }
+ updatePlaybackState();
+ return item;
+ }
+
+ public PlaylistItem remove(String iid) {
+ if (DEBUG) {
+ log("remove: iid=" + iid);
+ }
+ checkPlayerAndSession();
+ return removeItem(iid, MediaItemStatus.PLAYBACK_STATE_CANCELED);
+ }
+
+ public PlaylistItem seek(String iid, long pos) {
+ if (DEBUG) {
+ log("seek: iid=" + iid +", pos=" + pos);
+ }
+ checkPlayerAndSession();
+ // seeking on pending items are not yet supported
+ checkItemCurrent(iid);
+
+ PlaylistItem item = getCurrentItem();
+ if (pos != item.getPosition()) {
+ item.setPosition(pos);
+ if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
+ || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+ mPlayer.seek(item);
+ }
+ }
+ return item;
+ }
+
+ public PlaylistItem getStatus(String iid) {
+ checkPlayerAndSession();
+
+ // This should only be called for local player. Remote player is
+ // asynchronous, need to use updateStatus() instead.
+ if (mPlayer.isRemotePlayback()) {
+ throw new IllegalStateException(
+ "getStatus should not be called on remote player!");
+ }
+
+ for (PlaylistItem item : mPlaylist) {
+ if (item.getItemId().equals(iid)) {
+ if (item == getCurrentItem()) {
+ mPlayer.getStatus(item, false);
+ }
+ return item;
+ }
+ }
+ return null;
+ }
+
+ public void pause() {
+ if (DEBUG) {
+ log("pause");
+ }
+ mPaused = true;
+ updatePlaybackState();
+ }
+
+ public void resume() {
+ if (DEBUG) {
+ log("resume");
+ }
+ mPaused = false;
+ updatePlaybackState();
+ }
+
+ public void stop() {
+ if (DEBUG) {
+ log("stop");
+ }
+ mPlayer.stop();
+ mPlaylist.clear();
+ mPaused = false;
+ updateStatus();
+ }
+
+ public String startSession() {
+ if (!mSessionValid) {
+ mSessionId++;
+ mItemId = 0;
+ mPaused = false;
+ mSessionValid = true;
+ return Integer.toString(mSessionId);
+ }
+ return null;
+ }
+
+ public boolean endSession() {
+ if (mSessionValid) {
+ mSessionValid = false;
+ return true;
+ }
+ return false;
+ }
+
+ public MediaSessionStatus getSessionStatus(String sid) {
+ int sessionState = (sid != null && sid.equals(mSessionId)) ?
+ MediaSessionStatus.SESSION_STATE_ACTIVE :
+ MediaSessionStatus.SESSION_STATE_INVALIDATED;
+
+ return new MediaSessionStatus.Builder(sessionState)
+ .setQueuePaused(mPaused)
+ .build();
+ }
+
+ // Suspend the playback manager. Put the current item back into PENDING
+ // state, and remember the current playback position. Called when switching
+ // to a different player (route).
+ public void suspend(long pos) {
+ for (PlaylistItem item : mPlaylist) {
+ item.setRemoteItemId(null);
+ item.setDuration(0);
+ }
+ PlaylistItem item = getCurrentItem();
+ if (DEBUG) {
+ log("suspend: item=" + item + ", pos=" + pos);
+ }
+ if (item != null) {
+ if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
+ || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+ item.setState(MediaItemStatus.PLAYBACK_STATE_PENDING);
+ item.setPosition(pos);
+ }
+ }
+ }
+
+ // Unsuspend the playback manager. Restart playback on new player (route).
+ // This will resume playback of current item. Furthermore, if the new player
+ // supports queuing, playlist will be re-established on the remote player.
+ public void unsuspend() {
+ if (DEBUG) {
+ log("unsuspend");
+ }
+ if (mPlayer.isQueuingSupported()) {
+ for (PlaylistItem item : mPlaylist) {
+ mPlayer.enqueue(item);
+ }
+ }
+ updatePlaybackState();
+ }
+
+ // Player.Callback
+ @Override
+ public void onError() {
+ finishItem(true);
+ }
+
+ @Override
+ public void onCompletion() {
+ finishItem(false);
+ }
+
+ @Override
+ public void onPlaylistChanged() {
+ // Playlist has changed, update the cached playlist
+ updateStatus();
+ }
+
+ @Override
+ public void onPlaylistReady() {
+ // Notify activity to update Ui
+ if (mCallback != null) {
+ mCallback.onStatusChanged();
+ }
+ }
+
+ private void log(String message) {
+ Log.d(TAG, mName + ": " + message);
+ }
+
+ private void checkPlayer() {
+ if (mPlayer == null) {
+ throw new IllegalStateException("Player not set!");
+ }
+ }
+
+ private void checkSession() {
+ if (!mSessionValid) {
+ throw new IllegalStateException("Session not set!");
+ }
+ }
+
+ private void checkPlayerAndSession() {
+ checkPlayer();
+ checkSession();
+ }
+
+ private void checkItemCurrent(String iid) {
+ PlaylistItem item = getCurrentItem();
+ if (item == null || !item.getItemId().equals(iid)) {
+ throw new IllegalArgumentException("Item is not current!");
+ }
+ }
+
+ private void updatePlaybackState() {
+ PlaylistItem item = getCurrentItem();
+ if (item != null) {
+ if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING) {
+ item.setState(mPaused ? MediaItemStatus.PLAYBACK_STATE_PAUSED
+ : MediaItemStatus.PLAYBACK_STATE_PLAYING);
+ if (!mPlayer.isQueuingSupported()) {
+ mPlayer.play(item);
+ }
+ } else if (mPaused && item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) {
+ mPlayer.pause();
+ item.setState(MediaItemStatus.PLAYBACK_STATE_PAUSED);
+ } else if (!mPaused && item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+ mPlayer.resume();
+ item.setState(MediaItemStatus.PLAYBACK_STATE_PLAYING);
+ }
+ // notify client that item playback status has changed
+ if (mCallback != null) {
+ mCallback.onItemChanged(item);
+ }
+ }
+ updateStatus();
+ }
+
+ private PlaylistItem removeItem(String iid, int state) {
+ checkPlayerAndSession();
+ List<PlaylistItem> queue =
+ new ArrayList<PlaylistItem>(mPlaylist.size());
+ PlaylistItem found = null;
+ for (PlaylistItem item : mPlaylist) {
+ if (iid.equals(item.getItemId())) {
+ if (mPlayer.isQueuingSupported()) {
+ mPlayer.remove(item.getRemoteItemId());
+ } else if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
+ || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED){
+ mPlayer.stop();
+ }
+ item.setState(state);
+ found = item;
+ // notify client that item is now removed
+ if (mCallback != null) {
+ mCallback.onItemChanged(found);
+ }
+ } else {
+ queue.add(item);
+ }
+ }
+ if (found != null) {
+ mPlaylist = queue;
+ updatePlaybackState();
+ } else {
+ log("item not found");
+ }
+ return found;
+ }
+
+ private void finishItem(boolean error) {
+ PlaylistItem item = getCurrentItem();
+ if (item != null) {
+ removeItem(item.getItemId(), error ?
+ MediaItemStatus.PLAYBACK_STATE_ERROR :
+ MediaItemStatus.PLAYBACK_STATE_FINISHED);
+ updateStatus();
+ }
+ }
+
+ // set the Player that this playback manager will interact with
+ public void setPlayer(Player player) {
+ mPlayer = player;
+ checkPlayer();
+ mPlayer.setCallback(this);
+ }
+
+ // provide a callback interface to tell the UI when significant state changes occur
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public String toString() {
+ String result = "Media Queue: ";
+ if (!mPlaylist.isEmpty()) {
+ for (PlaylistItem item : mPlaylist) {
+ result += "\n" + item.toString();
+ }
+ } else {
+ result += "<empty>";
+ }
+ return result;
+ }
+
+ public interface Callback {
+ void onStatusChanged();
+ void onItemChanged(PlaylistItem item);
+ }
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/provider/SampleMediaRouteProvider.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/provider/SampleMediaRouteProvider.java
new file mode 100644
index 0000000..739e3ba
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/provider/SampleMediaRouteProvider.java
@@ -0,0 +1,602 @@
+/*
+ * 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 com.example.android.mediarouter.provider;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentFilter.MalformedMimeTypeException;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.MediaRouter;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.media.MediaControlIntent;
+import android.support.v7.media.MediaRouteDescriptor;
+import android.support.v7.media.MediaRouteProvider;
+import android.support.v7.media.MediaRouteProviderDescriptor;
+import android.support.v7.media.MediaRouter.ControlRequestCallback;
+import android.support.v7.media.MediaSessionStatus;
+import android.util.Log;
+
+import com.example.android.mediarouter.player.Player;
+import com.example.android.mediarouter.player.PlaylistItem;
+import com.example.android.mediarouter.R;
+import com.example.android.mediarouter.player.SessionManager;
+
+import java.util.ArrayList;
+
+/**
+ * Demonstrates how to create a custom media route provider.
+ *
+ * @see SampleMediaRouteProviderService
+ */
+public final class SampleMediaRouteProvider extends MediaRouteProvider {
+ private static final String TAG = "SampleMediaRouteProvider";
+
+ private static final String FIXED_VOLUME_ROUTE_ID = "fixed";
+ private static final String VARIABLE_VOLUME_BASIC_ROUTE_ID = "variable_basic";
+ private static final String VARIABLE_VOLUME_QUEUING_ROUTE_ID = "variable_queuing";
+ private static final String VARIABLE_VOLUME_SESSION_ROUTE_ID = "variable_session";
+ private static final int VOLUME_MAX = 10;
+
+ /**
+ * A custom media control intent category for special requests that are
+ * supported by this provider's routes.
+ */
+ public static final String CATEGORY_SAMPLE_ROUTE =
+ "com.example.android.mediarouteprovider.CATEGORY_SAMPLE_ROUTE";
+
+ /**
+ * A custom media control intent action for special requests that are
+ * supported by this provider's routes.
+ * <p>
+ * This particular request is designed to return a bundle of not very
+ * interesting statistics for demonstration purposes.
+ * </p>
+ *
+ * @see #DATA_PLAYBACK_COUNT
+ */
+ public static final String ACTION_GET_STATISTICS =
+ "com.example.android.mediarouteprovider.ACTION_GET_STATISTICS";
+
+ /**
+ * {@link #ACTION_GET_STATISTICS} result data: Number of times the
+ * playback action was invoked.
+ */
+ public static final String DATA_PLAYBACK_COUNT =
+ "com.example.android.mediarouteprovider.EXTRA_PLAYBACK_COUNT";
+
+ private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;
+ private static final ArrayList<IntentFilter> CONTROL_FILTERS_QUEUING;
+ private static final ArrayList<IntentFilter> CONTROL_FILTERS_SESSION;
+
+ static {
+ IntentFilter f1 = new IntentFilter();
+ f1.addCategory(CATEGORY_SAMPLE_ROUTE);
+ f1.addAction(ACTION_GET_STATISTICS);
+
+ IntentFilter f2 = new IntentFilter();
+ f2.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ f2.addAction(MediaControlIntent.ACTION_PLAY);
+ f2.addDataScheme("http");
+ f2.addDataScheme("https");
+ f2.addDataScheme("rtsp");
+ f2.addDataScheme("file");
+ addDataTypeUnchecked(f2, "video/*");
+
+ IntentFilter f3 = new IntentFilter();
+ f3.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ f3.addAction(MediaControlIntent.ACTION_SEEK);
+ f3.addAction(MediaControlIntent.ACTION_GET_STATUS);
+ f3.addAction(MediaControlIntent.ACTION_PAUSE);
+ f3.addAction(MediaControlIntent.ACTION_RESUME);
+ f3.addAction(MediaControlIntent.ACTION_STOP);
+
+ IntentFilter f4 = new IntentFilter();
+ f4.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ f4.addAction(MediaControlIntent.ACTION_ENQUEUE);
+ f4.addDataScheme("http");
+ f4.addDataScheme("https");
+ f4.addDataScheme("rtsp");
+ f4.addDataScheme("file");
+ addDataTypeUnchecked(f4, "video/*");
+
+ IntentFilter f5 = new IntentFilter();
+ f5.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ f5.addAction(MediaControlIntent.ACTION_REMOVE);
+
+ IntentFilter f6 = new IntentFilter();
+ f6.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ f6.addAction(MediaControlIntent.ACTION_START_SESSION);
+ f6.addAction(MediaControlIntent.ACTION_GET_SESSION_STATUS);
+ f6.addAction(MediaControlIntent.ACTION_END_SESSION);
+
+ CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
+ CONTROL_FILTERS_BASIC.add(f1);
+ CONTROL_FILTERS_BASIC.add(f2);
+ CONTROL_FILTERS_BASIC.add(f3);
+
+ CONTROL_FILTERS_QUEUING =
+ new ArrayList<IntentFilter>(CONTROL_FILTERS_BASIC);
+ CONTROL_FILTERS_QUEUING.add(f4);
+ CONTROL_FILTERS_QUEUING.add(f5);
+
+ CONTROL_FILTERS_SESSION =
+ new ArrayList<IntentFilter>(CONTROL_FILTERS_QUEUING);
+ CONTROL_FILTERS_SESSION.add(f6);
+ }
+
+ private static void addDataTypeUnchecked(IntentFilter filter, String type) {
+ try {
+ filter.addDataType(type);
+ } catch (MalformedMimeTypeException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private int mVolume = 5;
+ private int mEnqueueCount;
+
+ public SampleMediaRouteProvider(Context context) {
+ super(context);
+
+ publishRoutes();
+ }
+
+ @Override
+ public RouteController onCreateRouteController(String routeId) {
+ return new SampleRouteController(routeId);
+ }
+
+ private void publishRoutes() {
+ Resources r = getContext().getResources();
+
+ MediaRouteDescriptor routeDescriptor1 = new MediaRouteDescriptor.Builder(
+ FIXED_VOLUME_ROUTE_ID,
+ r.getString(R.string.fixed_volume_route_name))
+ .setDescription(r.getString(R.string.sample_route_description))
+ .addControlFilters(CONTROL_FILTERS_BASIC)
+ .setPlaybackStream(AudioManager.STREAM_MUSIC)
+ .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
+ .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED)
+ .setVolume(VOLUME_MAX)
+ .build();
+
+ MediaRouteDescriptor routeDescriptor2 = new MediaRouteDescriptor.Builder(
+ VARIABLE_VOLUME_BASIC_ROUTE_ID,
+ r.getString(R.string.variable_volume_basic_route_name))
+ .setDescription(r.getString(R.string.sample_route_description))
+ .addControlFilters(CONTROL_FILTERS_BASIC)
+ .setPlaybackStream(AudioManager.STREAM_MUSIC)
+ .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
+ .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
+ .setVolumeMax(VOLUME_MAX)
+ .setVolume(mVolume)
+ .build();
+
+ MediaRouteDescriptor routeDescriptor3 = new MediaRouteDescriptor.Builder(
+ VARIABLE_VOLUME_QUEUING_ROUTE_ID,
+ r.getString(R.string.variable_volume_queuing_route_name))
+ .setDescription(r.getString(R.string.sample_route_description))
+ .addControlFilters(CONTROL_FILTERS_QUEUING)
+ .setPlaybackStream(AudioManager.STREAM_MUSIC)
+ .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
+ .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
+ .setVolumeMax(VOLUME_MAX)
+ .setVolume(mVolume)
+ .build();
+
+ MediaRouteDescriptor routeDescriptor4 = new MediaRouteDescriptor.Builder(
+ VARIABLE_VOLUME_SESSION_ROUTE_ID,
+ r.getString(R.string.variable_volume_session_route_name))
+ .setDescription(r.getString(R.string.sample_route_description))
+ .addControlFilters(CONTROL_FILTERS_SESSION)
+ .setPlaybackStream(AudioManager.STREAM_MUSIC)
+ .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
+ .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
+ .setVolumeMax(VOLUME_MAX)
+ .setVolume(mVolume)
+ .build();
+
+ MediaRouteProviderDescriptor providerDescriptor =
+ new MediaRouteProviderDescriptor.Builder()
+ .addRoute(routeDescriptor1)
+ .addRoute(routeDescriptor2)
+ .addRoute(routeDescriptor3)
+ .addRoute(routeDescriptor4)
+ .build();
+ setDescriptor(providerDescriptor);
+ }
+
+ private final class SampleRouteController extends MediaRouteProvider.RouteController {
+ private final String mRouteId;
+ private final SessionManager mSessionManager = new SessionManager("mrp");
+ private final Player mPlayer;
+ private PendingIntent mSessionReceiver;
+
+ public SampleRouteController(String routeId) {
+ mRouteId = routeId;
+ mPlayer = Player.create(getContext(), null);
+ mSessionManager.setPlayer(mPlayer);
+ mSessionManager.setCallback(new SessionManager.Callback() {
+ @Override
+ public void onStatusChanged() {
+ }
+
+ @Override
+ public void onItemChanged(PlaylistItem item) {
+ handleStatusChange(item);
+ }
+ });
+ Log.d(TAG, mRouteId + ": Controller created");
+ }
+
+ @Override
+ public void onRelease() {
+ Log.d(TAG, mRouteId + ": Controller released");
+ mPlayer.release();
+ }
+
+ @Override
+ public void onSelect() {
+ Log.d(TAG, mRouteId + ": Selected");
+ mPlayer.connect(null);
+ }
+
+ @Override
+ public void onUnselect() {
+ Log.d(TAG, mRouteId + ": Unselected");
+ mPlayer.release();
+ }
+
+ @Override
+ public void onSetVolume(int volume) {
+ Log.d(TAG, mRouteId + ": Set volume to " + volume);
+ if (!mRouteId.equals(FIXED_VOLUME_ROUTE_ID)) {
+ setVolumeInternal(volume);
+ }
+ }
+
+ @Override
+ public void onUpdateVolume(int delta) {
+ Log.d(TAG, mRouteId + ": Update volume by " + delta);
+ if (!mRouteId.equals(FIXED_VOLUME_ROUTE_ID)) {
+ setVolumeInternal(mVolume + delta);
+ }
+ }
+
+ @Override
+ public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
+ Log.d(TAG, mRouteId + ": Received control request " + intent);
+ String action = intent.getAction();
+ if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
+ boolean success = false;
+ if (action.equals(MediaControlIntent.ACTION_PLAY)) {
+ success = handlePlay(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_ENQUEUE)) {
+ success = handleEnqueue(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_REMOVE)) {
+ success = handleRemove(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_SEEK)) {
+ success = handleSeek(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_GET_STATUS)) {
+ success = handleGetStatus(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_PAUSE)) {
+ success = handlePause(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_RESUME)) {
+ success = handleResume(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_STOP)) {
+ success = handleStop(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_START_SESSION)) {
+ success = handleStartSession(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_GET_SESSION_STATUS)) {
+ success = handleGetSessionStatus(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_END_SESSION)) {
+ success = handleEndSession(intent, callback);
+ }
+ Log.d(TAG, mSessionManager.toString());
+ return success;
+ }
+
+ if (action.equals(ACTION_GET_STATISTICS)
+ && intent.hasCategory(CATEGORY_SAMPLE_ROUTE)) {
+ Bundle data = new Bundle();
+ data.putInt(DATA_PLAYBACK_COUNT, mEnqueueCount);
+ if (callback != null) {
+ callback.onResult(data);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void setVolumeInternal(int volume) {
+ if (volume >= 0 && volume <= VOLUME_MAX) {
+ mVolume = volume;
+ Log.d(TAG, mRouteId + ": New volume is " + mVolume);
+ AudioManager audioManager =
+ (AudioManager)getContext().getSystemService(Context.AUDIO_SERVICE);
+ audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
+ publishRoutes();
+ }
+ }
+
+ private boolean handlePlay(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ if (sid != null && !sid.equals(mSessionManager.getSessionId())) {
+ Log.d(TAG, "handlePlay fails because of bad sid="+sid);
+ return false;
+ }
+ if (mSessionManager.hasSession()) {
+ mSessionManager.stop();
+ }
+ return handleEnqueue(intent, callback);
+ }
+
+ private boolean handleEnqueue(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ if (sid != null && !sid.equals(mSessionManager.getSessionId())) {
+ Log.d(TAG, "handleEnqueue fails because of bad sid="+sid);
+ return false;
+ }
+
+ Uri uri = intent.getData();
+ if (uri == null) {
+ Log.d(TAG, "handleEnqueue fails because of bad uri="+uri);
+ return false;
+ }
+
+ boolean enqueue = intent.getAction().equals(MediaControlIntent.ACTION_ENQUEUE);
+ String mime = intent.getType();
+ long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
+ Bundle metadata = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_METADATA);
+ Bundle headers = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_HTTP_HEADERS);
+ PendingIntent receiver = (PendingIntent)intent.getParcelableExtra(
+ MediaControlIntent.EXTRA_ITEM_STATUS_UPDATE_RECEIVER);
+
+ Log.d(TAG, mRouteId + ": Received " + (enqueue?"enqueue":"play") + " request"
+ + ", uri=" + uri
+ + ", mime=" + mime
+ + ", sid=" + sid
+ + ", pos=" + pos
+ + ", metadata=" + metadata
+ + ", headers=" + headers
+ + ", receiver=" + receiver);
+ PlaylistItem item = mSessionManager.add(uri, mime, receiver);
+ if (callback != null) {
+ if (item != null) {
+ Bundle result = new Bundle();
+ result.putString(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId());
+ result.putString(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId());
+ result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
+ item.getStatus().asBundle());
+ callback.onResult(result);
+ } else {
+ callback.onError("Failed to open " + uri.toString(), null);
+ }
+ }
+ mEnqueueCount +=1;
+ return true;
+ }
+
+ private boolean handleRemove(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ if (sid == null || !sid.equals(mSessionManager.getSessionId())) {
+ return false;
+ }
+
+ String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
+ PlaylistItem item = mSessionManager.remove(iid);
+ if (callback != null) {
+ if (item != null) {
+ Bundle result = new Bundle();
+ result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
+ item.getStatus().asBundle());
+ callback.onResult(result);
+ } else {
+ callback.onError("Failed to remove" +
+ ", sid=" + sid + ", iid=" + iid, null);
+ }
+ }
+ return (item != null);
+ }
+
+ private boolean handleSeek(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ if (sid == null || !sid.equals(mSessionManager.getSessionId())) {
+ return false;
+ }
+
+ String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
+ long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
+ Log.d(TAG, mRouteId + ": Received seek request, pos=" + pos);
+ PlaylistItem item = mSessionManager.seek(iid, pos);
+ if (callback != null) {
+ if (item != null) {
+ Bundle result = new Bundle();
+ result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
+ item.getStatus().asBundle());
+ callback.onResult(result);
+ } else {
+ callback.onError("Failed to seek" +
+ ", sid=" + sid + ", iid=" + iid + ", pos=" + pos, null);
+ }
+ }
+ return (item != null);
+ }
+
+ private boolean handleGetStatus(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
+ Log.d(TAG, mRouteId + ": Received getStatus request, sid=" + sid + ", iid=" + iid);
+ PlaylistItem item = mSessionManager.getStatus(iid);
+ if (callback != null) {
+ if (item != null) {
+ Bundle result = new Bundle();
+ result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
+ item.getStatus().asBundle());
+ callback.onResult(result);
+ } else {
+ callback.onError("Failed to get status" +
+ ", sid=" + sid + ", iid=" + iid, null);
+ }
+ }
+ return (item != null);
+ }
+
+ private boolean handlePause(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
+ mSessionManager.pause();
+ if (callback != null) {
+ if (success) {
+ callback.onResult(new Bundle());
+ handleSessionStatusChange(sid);
+ } else {
+ callback.onError("Failed to pause, sid=" + sid, null);
+ }
+ }
+ return success;
+ }
+
+ private boolean handleResume(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
+ mSessionManager.resume();
+ if (callback != null) {
+ if (success) {
+ callback.onResult(new Bundle());
+ handleSessionStatusChange(sid);
+ } else {
+ callback.onError("Failed to resume, sid=" + sid, null);
+ }
+ }
+ return success;
+ }
+
+ private boolean handleStop(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
+ mSessionManager.stop();
+ if (callback != null) {
+ if (success) {
+ callback.onResult(new Bundle());
+ handleSessionStatusChange(sid);
+ } else {
+ callback.onError("Failed to stop, sid=" + sid, null);
+ }
+ }
+ return success;
+ }
+
+ private boolean handleStartSession(Intent intent, ControlRequestCallback callback) {
+ String sid = mSessionManager.startSession();
+ Log.d(TAG, "StartSession returns sessionId "+sid);
+ if (callback != null) {
+ if (sid != null) {
+ Bundle result = new Bundle();
+ result.putString(MediaControlIntent.EXTRA_SESSION_ID, sid);
+ result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS,
+ mSessionManager.getSessionStatus(sid).asBundle());
+ callback.onResult(result);
+ mSessionReceiver = (PendingIntent)intent.getParcelableExtra(
+ MediaControlIntent.EXTRA_SESSION_STATUS_UPDATE_RECEIVER);
+ handleSessionStatusChange(sid);
+ } else {
+ callback.onError("Failed to start session.", null);
+ }
+ }
+ return (sid != null);
+ }
+
+ private boolean handleGetSessionStatus(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+
+ MediaSessionStatus sessionStatus = mSessionManager.getSessionStatus(sid);
+ if (callback != null) {
+ if (sessionStatus != null) {
+ Bundle result = new Bundle();
+ result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS,
+ mSessionManager.getSessionStatus(sid).asBundle());
+ callback.onResult(result);
+ } else {
+ callback.onError("Failed to get session status, sid=" + sid, null);
+ }
+ }
+ return (sessionStatus != null);
+ }
+
+ private boolean handleEndSession(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId())
+ && mSessionManager.endSession();
+ if (callback != null) {
+ if (success) {
+ Bundle result = new Bundle();
+ MediaSessionStatus sessionStatus = new MediaSessionStatus.Builder(
+ MediaSessionStatus.SESSION_STATE_ENDED).build();
+ result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS, sessionStatus.asBundle());
+ callback.onResult(result);
+ handleSessionStatusChange(sid);
+ mSessionReceiver = null;
+ } else {
+ callback.onError("Failed to end session, sid=" + sid, null);
+ }
+ }
+ return success;
+ }
+
+ private void handleStatusChange(PlaylistItem item) {
+ if (item == null) {
+ item = mSessionManager.getCurrentItem();
+ }
+ if (item != null) {
+ PendingIntent receiver = item.getUpdateReceiver();
+ if (receiver != null) {
+ Intent intent = new Intent();
+ intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId());
+ intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId());
+ intent.putExtra(MediaControlIntent.EXTRA_ITEM_STATUS,
+ item.getStatus().asBundle());
+ try {
+ receiver.send(getContext(), 0, intent);
+ Log.d(TAG, mRouteId + ": Sending status update from provider");
+ } catch (PendingIntent.CanceledException e) {
+ Log.d(TAG, mRouteId + ": Failed to send status update!");
+ }
+ }
+ }
+ }
+
+ private void handleSessionStatusChange(String sid) {
+ if (mSessionReceiver != null) {
+ Intent intent = new Intent();
+ intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sid);
+ intent.putExtra(MediaControlIntent.EXTRA_SESSION_STATUS,
+ mSessionManager.getSessionStatus(sid).asBundle());
+ try {
+ mSessionReceiver.send(getContext(), 0, intent);
+ Log.d(TAG, mRouteId + ": Sending session status update from provider");
+ } catch (PendingIntent.CanceledException e) {
+ Log.d(TAG, mRouteId + ": Failed to send session status update!");
+ }
+ }
+ }
+ }
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/provider/SampleMediaRouteProviderService.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/provider/SampleMediaRouteProviderService.java
new file mode 100644
index 0000000..41a6cbd
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/provider/SampleMediaRouteProviderService.java
@@ -0,0 +1,35 @@
+/*
+ * 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 com.example.android.mediarouter.provider;
+
+import android.support.v7.media.MediaRouteProvider;
+import android.support.v7.media.MediaRouteProviderService;
+
+import com.example.android.mediarouter.provider.SampleMediaRouteProvider;
+
+/**
+ * Demonstrates how to register a custom media route provider service
+ * using the support library.
+ *
+ * @see com.example.android.mediarouter.provider.SampleMediaRouteProvider
+ */
+public class SampleMediaRouteProviderService extends MediaRouteProviderService {
+ @Override
+ public MediaRouteProvider onCreateMediaRouteProvider() {
+ return new SampleMediaRouteProvider(this);
+ }
+}
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_pause.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_pause.png
new file mode 100644
index 0000000..d30ba3c
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_pause.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_play.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_play.png
new file mode 100644
index 0000000..869f001
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_play.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_stop.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_stop.png
new file mode 100644
index 0000000..2b6c0f4
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_stop.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_launcher.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100755
index 0000000..d340114
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_pause.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_pause.png
new file mode 100644
index 0000000..1d465a4
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_pause.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_play.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_play.png
new file mode 100644
index 0000000..2746d17
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_play.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_stop.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_stop.png
new file mode 100644
index 0000000..a0ff136
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_stop.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_menu_add.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_menu_add.png
new file mode 100644
index 0000000..444e8a5
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_menu_add.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_menu_delete.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_menu_delete.png
new file mode 100644
index 0000000..24d8f6a
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_menu_delete.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/tile.9.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/tile.9.png
new file mode 100644
index 0000000..1358628
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/tile.9.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_pause.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_pause.png
new file mode 100644
index 0000000..2c96c7b
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_pause.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_play.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_play.png
new file mode 100644
index 0000000..5f3bf86
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_play.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_stop.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_stop.png
new file mode 100644
index 0000000..ddaf37a
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_stop.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_launcher.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100755
index 0000000..6af9981
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_pause.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_pause.png
new file mode 100644
index 0000000..3e6b2a1
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_pause.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_play.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_play.png
new file mode 100644
index 0000000..7966bbc
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_play.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_stop.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_stop.png
new file mode 100644
index 0000000..8ea7efe
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_stop.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_menu_add.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_menu_add.png
new file mode 100644
index 0000000..361c7c4
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_menu_add.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_menu_delete.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_menu_delete.png
new file mode 100644
index 0000000..e2c8700
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_menu_delete.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_pause.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_pause.png
new file mode 100644
index 0000000..504389a
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_pause.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_play.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_play.png
new file mode 100644
index 0000000..7f709bb
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_play.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_stop.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_stop.png
new file mode 100644
index 0000000..2b07de4
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_stop.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_launcher.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..16bd612
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_pause.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_pause.png
new file mode 100644
index 0000000..c3b376a
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_pause.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_play.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_play.png
new file mode 100644
index 0000000..df59947
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_play.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_stop.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_stop.png
new file mode 100644
index 0000000..f42d525
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_stop.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_launcher.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..4b1d920
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_suggestions_add.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_suggestions_add.png
new file mode 100644
index 0000000..b880d40
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_suggestions_add.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_suggestions_delete.png b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_suggestions_delete.png
new file mode 100644
index 0000000..f9e2702
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_suggestions_delete.png
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable/list_background.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable/list_background.xml
new file mode 100644
index 0000000..be7240b
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/drawable/list_background.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true"
+ android:drawable="@color/list_highlight_color" />
+ <item
+ android:drawable="@android:color/transparent" />
+</selector>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout-land/grid_layout_2.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout-land/grid_layout_2.xml
new file mode 100644
index 0000000..da16123
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout-land/grid_layout_2.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<android.support.v7.widget.GridLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:useDefaultMargins="true"
+ android:alignmentMode="alignBounds"
+ android:rowOrderPreserved="false"
+ android:columnCount="4"
+ >
+ <TextView
+ android:text="Email setup"
+ android:textSize="32dip"
+ android:layout_columnSpan="4"
+ android:layout_gravity="center_horizontal"
+ />
+ <TextView
+ android:text="You can configure email in a few simple steps:"
+ android:textSize="16dip"
+ android:layout_columnSpan="4"
+ android:layout_gravity="left"
+ />
+ <TextView
+ android:text="Email address:"
+ android:layout_gravity="right"
+ />
+ <EditText
+ android:ems="10"
+ />
+ <TextView
+ android:text="Password:"
+ android:layout_column="0"
+ android:layout_gravity="right"
+ />
+ <EditText
+ android:ems="8"
+ />
+ <Button
+ android:text="Manual setup"
+ android:layout_row="5"
+ android:layout_column="3"
+ />
+ <Button
+ android:text="Next"
+ android:layout_column="3"
+ android:layout_gravity="fill_horizontal"
+ />
+</android.support.v7.widget.GridLayout>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/activity_main.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/activity_main.xml
new file mode 100755
index 0000000..be1aa49
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,36 @@
+<!--
+ Copyright 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout style="@style/Widget.SampleMessageTile"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView style="@style/Widget.SampleMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/horizontal_page_margin"
+ android:layout_marginRight="@dimen/horizontal_page_margin"
+ android:layout_marginTop="@dimen/vertical_page_margin"
+ android:layout_marginBottom="@dimen/vertical_page_margin"
+ android:text="@string/intro_message" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/media_item.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/media_item.xml
new file mode 100644
index 0000000..3a14861
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/media_item.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<!-- Layout for list item in Library or Playlist view. Displays ImageButton
+ instead of radio button to the right of the item. -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:clickable="true"
+ android:background="@drawable/list_background"
+ android:gravity="center_vertical">
+
+ <ImageButton android:id="@+id/item_action"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="48dp"
+ android:minHeight="48dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:background="@null"/>
+
+ <TextView android:id="@+id/item_text"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_centerVertical="true"
+ android:layout_toLeftOf="@id/item_action"
+ android:layout_gravity="left"
+ android:gravity="left"/>
+</RelativeLayout>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/overlay_display_window.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/overlay_display_window.xml
new file mode 100644
index 0000000..36b4a0d
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/overlay_display_window.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#000000">
+ <TextureView android:id="@+id/overlay_display_window_texture"
+ android:layout_width="0px"
+ android:layout_height="0px" />
+ <TextView android:id="@+id/overlay_display_window_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|center_horizontal" />
+</FrameLayout>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/sample_media_router.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/sample_media_router.xml
new file mode 100644
index 0000000..1618418
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/sample_media_router.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- See corresponding Java code MainActivity.java. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical">
+ <!-- Tabs for media library, playlist and statistics -->
+ <TabHost android:id="@+id/tabHost"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1">
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <TabWidget android:id="@android:id/tabs"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ <FrameLayout android:id="@android:id/tabcontent"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <LinearLayout android:id="@+id/tab1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <ListView android:id="@+id/media"
+ android:listSelector="@drawable/list_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+ </LinearLayout>
+
+ <LinearLayout android:id="@+id/tab2"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+ <ListView android:id="@+id/playlist"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+ </LinearLayout>
+
+ <LinearLayout android:id="@+id/tab3"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical">
+ <TextView android:id="@+id/info"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceMedium"/>
+ </LinearLayout>
+ </FrameLayout>
+ </LinearLayout>
+ </TabHost>
+
+ <!-- Control buttons for the currently selected route. -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0">
+
+ <SeekBar android:id="@+id/seekbar"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:max="100"
+ android:progress="0"
+ android:layout_gravity="center"
+ android:layout_weight="1"/>
+
+ <ImageButton android:id="@+id/pause_resume_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:layout_gravity="right"
+ android:minWidth="48dp"
+ android:minHeight="48dp"
+ android:background="@null"
+ android:src="@drawable/ic_action_pause" />
+
+ <ImageButton android:id="@+id/stop_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:layout_gravity="right"
+ android:minWidth="48dp"
+ android:minHeight="48dp"
+ android:background="@null"
+ android:src="@drawable/ic_action_stop" />
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <!-- Some content for visual interest in the case where no presentation is showing. -->
+ <FrameLayout android:id="@+id/player"
+ android:background="#ff000000"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center">
+ <SurfaceView android:id="@+id/surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+ </LinearLayout>
+ <TextView
+ android:textColor="#ffaaaaaa"
+ android:text="@string/sample_media_route_activity_local"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|center_horizontal" />
+ </FrameLayout>
+</LinearLayout>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/sample_media_router_presentation.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/sample_media_router_presentation.xml
new file mode 100644
index 0000000..f029627
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/layout/sample_media_router_presentation.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- The content that we show on secondary displays.
+ See corresponding Java code PresentationWithMediaRouterActivity.java. -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#ff000000">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center">
+ <SurfaceView android:id="@+id/surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+ </LinearLayout>
+ <TextView
+ android:textColor="#ffaaaaaa"
+ android:text="@string/sample_media_route_activity_presentation"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|center_horizontal" />
+</FrameLayout>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/menu/sample_media_router_menu.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/menu/sample_media_router_menu.xml
new file mode 100644
index 0000000..8057fa8
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/menu/sample_media_router_menu.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 Google Inc.
+
+ 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:id="@+id/media_route_menu_item"
+ android:title="@string/media_route_menu_title"
+ app:showAsAction="always"
+ app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"/>
+</menu>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values-sw600dp/template-dimens.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values-sw600dp/template-dimens.xml
new file mode 100644
index 0000000..22074a2
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values-sw600dp/template-dimens.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <!-- Semantic definitions -->
+
+ <dimen name="horizontal_page_margin">@dimen/margin_huge</dimen>
+ <dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
+
+</resources>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values-sw600dp/template-styles.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values-sw600dp/template-styles.xml
new file mode 100644
index 0000000..03d1974
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values-sw600dp/template-styles.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <style name="Widget.SampleMessage">
+ <item name="android:textAppearance">?android:textAppearanceLarge</item>
+ <item name="android:lineSpacingMultiplier">1.2</item>
+ <item name="android:shadowDy">-6.5</item>
+ </style>
+
+</resources>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/arrays.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..8d658eb
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/arrays.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+ <string-array name="media_names">
+ <item>Big Buck Bunny</item>
+ <item>Elephants Dream</item>
+ <item>Sintel</item>
+ <item>Tears of Steel</item>
+ </string-array>
+
+ <string-array name="media_uris">
+ <item>http://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4</item>
+ <item>http://archive.org/download/ElephantsDream_277/elephant_dreams_640_512kb.mp4</item>
+ <item>http://archive.org/download/Sintel/sintel-2048-stereo_512kb.mp4</item>
+ <item>http://archive.org/download/Tears-of-Steel/tears_of_steel_720p.mp4</item>
+ </string-array>
+</resources>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/base-strings.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/base-strings.xml
new file mode 100644
index 0000000..8b494cc
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/base-strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 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.
+-->
+
+
+
+<resources>
+ <string name="app_name">MediaRouter</string>
+ <string name="intro_message">
+ <![CDATA[
+
+ Demonstrates how to create a custom media route provider.
+
+ ]]>
+ </string>
+</resources>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/colors.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/colors.xml
new file mode 100644
index 0000000..13cf3b5
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<resources>
+ <drawable name="blue">#770000ff</drawable>
+ <color name="list_highlight_color">#ccc</color>
+</resources>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/strings.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c750ce1
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/strings.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+
+ <string name="sample_media_router_text">This activity demonstrates how to
+ use MediaRouter from the support library. Select a route from the action bar.</string>
+ <string name="media_route_menu_title">Play on...</string>
+
+ <string name="library_tab_text">Library</string>
+ <string name="playlist_tab_text">Playlist</string>
+ <string name="statistics_tab_text">Statistics</string>
+
+ <string name="sample_media_route_provider_service">Media Route Provider Service Support Library Sample</string>
+ <string name="fixed_volume_route_name">Fixed Volume Remote Playback Route</string>
+ <string name="variable_volume_basic_route_name">Variable Volume (Basic) Remote Playback Route</string>
+ <string name="variable_volume_queuing_route_name">Variable Volume (Queuing) Remote Playback Route</string>
+ <string name="variable_volume_session_route_name">Variable Volume (Session) Remote Playback Route</string>
+ <string name="sample_route_description">Sample route</string>
+
+ <string name="sample_media_route_provider_remote">Remote Playback (Simulated)</string>
+ <string name="sample_media_route_activity_local">Local Playback</string>
+ <string name="sample_media_route_activity_presentation">Local Playback on Presentation Display</string>
+ <string name="playlist_item_added_text">Added to playlist!</string>
+ <string name="playlist_item_removed_text">Removed from playlist</string>
+
+</resources>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/template-dimens.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/template-dimens.xml
new file mode 100644
index 0000000..39e710b
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/template-dimens.xml
@@ -0,0 +1,32 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <!-- Define standard dimensions to comply with Holo-style grids and rhythm. -->
+
+ <dimen name="margin_tiny">4dp</dimen>
+ <dimen name="margin_small">8dp</dimen>
+ <dimen name="margin_medium">16dp</dimen>
+ <dimen name="margin_large">32dp</dimen>
+ <dimen name="margin_huge">64dp</dimen>
+
+ <!-- Semantic definitions -->
+
+ <dimen name="horizontal_page_margin">@dimen/margin_medium</dimen>
+ <dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
+
+</resources>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/template-styles.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/template-styles.xml
new file mode 100644
index 0000000..404623e
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/src/main/res/values/template-styles.xml
@@ -0,0 +1,42 @@
+<!--
+ Copyright 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.
+ -->
+
+<resources>
+
+ <!-- Activity themes -->
+
+ <style name="Theme.Base" parent="android:Theme.Holo.Light" />
+
+ <style name="Theme.Sample" parent="Theme.Base" />
+
+ <style name="AppTheme" parent="Theme.Sample" />
+ <!-- Widget styling -->
+
+ <style name="Widget" />
+
+ <style name="Widget.SampleMessage">
+ <item name="android:textAppearance">?android:textAppearanceMedium</item>
+ <item name="android:lineSpacingMultiplier">1.1</item>
+ </style>
+
+ <style name="Widget.SampleMessageTile">
+ <item name="android:background">@drawable/tile</item>
+ <item name="android:shadowColor">#7F000000</item>
+ <item name="android:shadowDy">-3.5</item>
+ <item name="android:shadowRadius">2</item>
+ </style>
+
+</resources>
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/tests/AndroidManifest.xml b/prebuilts/gradle/MediaRouter/MediaRouterSample/tests/AndroidManifest.xml
new file mode 100644
index 0000000..f60a1d7
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/tests/AndroidManifest.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 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.
+-->
+
+
+
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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 name must be unique so suffix with "tests" so package loader doesn't ignore us -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.mediarouter.tests"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk
+ android:minSdkVersion="18"
+ android:targetSdkVersion="19" />
+
+ <!-- We add an application tag here just so that we can indicate that
+ this package needs to link against the android.test library,
+ which is needed when building test cases. -->
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <!--
+ Specifies the instrumentation test runner used to run the tests.
+ -->
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.example.android.mediarouter"
+ android:label="Tests for com.example.android.mediarouter" />
+
+</manifest>
\ No newline at end of file
diff --git a/prebuilts/gradle/MediaRouter/MediaRouterSample/tests/src/com/example/android/mediarouter/tests/SampleTests.java b/prebuilts/gradle/MediaRouter/MediaRouterSample/tests/src/com/example/android/mediarouter/tests/SampleTests.java
new file mode 100644
index 0000000..e62998a
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/MediaRouterSample/tests/src/com/example/android/mediarouter/tests/SampleTests.java
@@ -0,0 +1,79 @@
+/*
+* Copyright 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.
+*/
+
+
+
+/*
+* 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 com.example.android.mediarouter.tests;
+
+import com.example.android.mediarouter.*;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+/**
+* Tests for MediaRouter sample.
+*/
+public class SampleTests extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private MainActivity mTestActivity;
+ private MediaRouterFragment mTestFragment;
+
+ public SampleTests() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // Starts the activity under test using the default Intent with:
+ // action = {@link Intent#ACTION_MAIN}
+ // flags = {@link Intent#FLAG_ACTIVITY_NEW_TASK}
+ // All other fields are null or empty.
+ mTestActivity = getActivity();
+ mTestFragment = (MediaRouterFragment)
+ mTestActivity.getSupportFragmentManager().getFragments().get(1);
+ }
+
+ /**
+ * Test if the test fixture has been set up correctly.
+ */
+ public void testPreconditions() {
+ //Try to add a message to add context to your assertions. These messages will be shown if
+ //a tests fails and make it easy to understand why a test failed
+ assertNotNull("mTestActivity is null", mTestActivity);
+ assertNotNull("mTestFragment is null", mTestFragment);
+ }
+
+ /**
+ * Add more tests below.
+ */
+
+}
\ No newline at end of file
diff --git a/prebuilts/gradle/MediaRouter/README.txt b/prebuilts/gradle/MediaRouter/README.txt
new file mode 100644
index 0000000..9616c58
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/README.txt
@@ -0,0 +1,18 @@
+Build Instructions
+-------------------
+
+This sample uses the Gradle build system. To build this project, use the
+"gradlew build" command or use "Import Project" in Android Studio.
+
+To see a list of all available commands, run "gradlew tasks".
+
+Dependencies
+-------------
+
+- Android SDK Build-tools v18.1
+- Android Support Repository v2
+
+Dependencies are available for download via the Android SDK Manager.
+
+Android Studio is available for download at:
+ http://developer.android.com/sdk/installing/studio.html
diff --git a/prebuilts/gradle/MediaRouter/build.gradle b/prebuilts/gradle/MediaRouter/build.gradle
new file mode 100644
index 0000000..5cf5d3d
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/build.gradle
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/prebuilts/gradle/MediaRouter/gradle/wrapper/gradle-wrapper.jar b/prebuilts/gradle/MediaRouter/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/prebuilts/gradle/MediaRouter/gradle/wrapper/gradle-wrapper.properties b/prebuilts/gradle/MediaRouter/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..5de946b
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip
diff --git a/prebuilts/gradle/MediaRouter/gradlew b/prebuilts/gradle/MediaRouter/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/prebuilts/gradle/MediaRouter/gradlew.bat b/prebuilts/gradle/MediaRouter/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/prebuilts/gradle/MediaRouter/settings.gradle b/prebuilts/gradle/MediaRouter/settings.gradle
new file mode 100644
index 0000000..fff93ae
--- /dev/null
+++ b/prebuilts/gradle/MediaRouter/settings.gradle
@@ -0,0 +1,4 @@
+
+
+
+include 'MediaRouterSample'