Merge "camera2: Add RAW capture sample application." into mnc-dev
diff --git a/background/JobScheduler/template-params.xml b/background/JobScheduler/template-params.xml
index a9e06de..2a6197b 100644
--- a/background/JobScheduler/template-params.xml
+++ b/background/JobScheduler/template-params.xml
@@ -23,7 +23,7 @@
<package>com.example.android.jobscheduler</package>
<!-- change minSdk if needed-->
- <minSdk>18</minSdk>
+ <minSdk>21</minSdk>
<compileSdkVersion>21</compileSdkVersion>
<strings>
diff --git a/background/alarms/RepeatingAlarm/template-params.xml b/background/alarms/RepeatingAlarm/template-params.xml
index b013a53..f2d68c5 100644
--- a/background/alarms/RepeatingAlarm/template-params.xml
+++ b/background/alarms/RepeatingAlarm/template-params.xml
@@ -21,7 +21,7 @@
<!--TODO: change minSdk if needed-->
- <minSdk>4</minSdk>
+ <minSdk>11</minSdk>
<strings>
<intro>
diff --git a/build.gradle b/build.gradle
index 5c72a6f..cdaf043 100644
--- a/build.gradle
+++ b/build.gradle
@@ -97,6 +97,8 @@
"admin/NfcProvisioning",
"admin/DeviceOwner",
"wearable/wear/XYZTouristAttractions",
+"connectivity/bluetooth/BluetoothAdvertisements",
+"wearable/wear/AlwaysOn",
]
List<String> taskNames = [
diff --git a/connectivity/bluetooth/BluetoothAdvertisements/Application/src/main/res/values/strings.xml b/connectivity/bluetooth/BluetoothAdvertisements/Application/src/main/res/values/strings.xml
index 0ea9c83..197178d 100644
--- a/connectivity/bluetooth/BluetoothAdvertisements/Application/src/main/res/values/strings.xml
+++ b/connectivity/bluetooth/BluetoothAdvertisements/Application/src/main/res/values/strings.xml
@@ -25,5 +25,6 @@
<string name="empty_list">No devices found - refresh to try again.</string>
<string name="seconds">seconds.</string>
<string name="scan_start_toast">Scanning for</string>
+ <string name="already_scanning">Scanning already started.</string>
</resources>
\ No newline at end of file
diff --git a/content/AutomaticBackup/Application/.gitignore b/content/AutomaticBackup/Application/.gitignore
new file mode 100644
index 0000000..6eb878d
--- /dev/null
+++ b/content/AutomaticBackup/Application/.gitignore
@@ -0,0 +1,16 @@
+# 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.
+src/template/
+src/common/
+build.gradle
diff --git a/content/AutomaticBackup/Application/proguard-project.txt b/content/AutomaticBackup/Application/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/content/AutomaticBackup/Application/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/content/AutomaticBackup/Application/src/main/AndroidManifest.xml b/content/AutomaticBackup/Application/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7dad1e0
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.autobackupsample" >
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme"
+ android:fullBackupContent="@xml/backup">
+ <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>
+ <activity android:name=".AddFileActivity" />
+ </application>
+
+</manifest>
diff --git a/content/AutomaticBackup/Application/src/main/java/com/example/android/autobackupsample/AddFileActivity.java b/content/AutomaticBackup/Application/src/main/java/com/example/android/autobackupsample/AddFileActivity.java
new file mode 100644
index 0000000..00cd1e3
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/java/com/example/android/autobackupsample/AddFileActivity.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2015 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.autobackupsample;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.Toast;
+
+import com.example.android.autobackupsample.MainActivityFragment;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+
+/**
+ * The purpose of AddFileActivity activity is to create a data file based on the
+ * file name and size parameters specified as an Intent external parameters or with the
+ * activity UI.
+ * <p/>
+ * The optional intent parameters are
+ * {@link com.example.android.autobackupsample.AddFileActivity#FILE_NAME} and
+ * {@link com.example.android.autobackupsample.AddFileActivity#FILE_SIZE_IN_BYTES}.
+ * {@link com.example.android.autobackupsample.AddFileActivity#FILE_STORAGE}.
+ * <p/>
+ * The activity will return an
+ * {@link com.example.android.autobackupsample.MainActivityFragment#ADD_FILE_RESULT_ERROR}
+ * if intent parameters are specified incorrectly or it will display Toast messages to the user
+ * if those parameters are specified via the activity UI.
+ */
+public class AddFileActivity extends Activity {
+
+ private static final String TAG = "AutoBackupSample";
+
+ /**
+ * The intent parameter that specifies a file name. The file name must be unique for the
+ * application internal directory.
+ */
+ public static final String FILE_NAME = "file_name";
+
+ /**
+ * The intent parameter that specifies a file size in bytes. The size must be a number
+ * larger or equal to 0.
+ */
+ public static final String FILE_SIZE_IN_BYTES = "file_size_in_bytes";
+
+ /**
+ * The file storage is an optional parameter. It should be one of these:
+ * "INTERNAL", "EXTERNAL", "DONOTBACKUP". The default option is "INTERNAL".
+ */
+ public static final String FILE_STORAGE = "file_storage";
+
+ /**
+ * A file size multiplier. It is used to calculate the total number of bytes to be added
+ * to the file.
+ */
+ private int mSizeMultiplier = 1;
+
+ /**
+ * Defines File Storage options.
+ */
+ private static enum FileStorage {
+ INTERNAL,
+ EXTERNAL,
+ DONOTBACKUP;
+ }
+
+ /**
+ * Contains a selected by a user file storage option.
+ */
+ private FileStorage mFileStorage = FileStorage.INTERNAL;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.add_file);
+ initFileSizeSpinner();
+ initFileStorageSpinner();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // If an intent has extra parameters, create the file and finish the activity.
+ if (getIntent().hasExtra(FILE_NAME) && getIntent().hasExtra(FILE_SIZE_IN_BYTES)) {
+ String fileName = getIntent().getStringExtra(FILE_NAME);
+ String sizeInBytesParamValue = getIntent().getStringExtra(FILE_SIZE_IN_BYTES);
+ String fileStorageParamValue = FileStorage.INTERNAL.toString();
+
+ if (getIntent().hasExtra(FILE_STORAGE)) {
+ fileStorageParamValue = getIntent().getStringExtra(FILE_STORAGE);
+ }
+
+ if (TextUtils.isEmpty(fileName) ||
+ isFileExists(fileName) ||
+ !isSizeValid(sizeInBytesParamValue) ||
+ !isFileStorageParamValid(fileStorageParamValue)) {
+ setResult(MainActivityFragment.ADD_FILE_RESULT_ERROR);
+ finish();
+ return;
+ }
+
+ mFileStorage = FileStorage.valueOf(fileStorageParamValue);
+
+ if (mFileStorage == FileStorage.EXTERNAL && !Utils.isExternalStorageAvailable()) {
+ setResult(MainActivityFragment.ADD_FILE_RESULT_ERROR);
+ finish();
+ return;
+ }
+
+ createFileWithRandomDataAndFinishActivity(fileName, mFileStorage,
+ sizeInBytesParamValue);
+ }
+ }
+
+ /**
+ * A handler function for a Create File button click event.
+ *
+ * @param view a reference to the Create File button view.
+ */
+ public void onCreateFileButtonClick(View view) {
+ EditText fileNameEditText = (EditText) findViewById(R.id.file_name);
+ EditText fileSizeEditText = (EditText) findViewById(R.id.file_size);
+ String fileName = fileNameEditText.getText().toString();
+ String fileSizeEditTextValue = fileSizeEditText.getText().toString();
+
+ if (TextUtils.isEmpty(fileName) || isFileExists(fileName)) {
+ Toast toast = Toast.makeText(this, getText(R.string.file_exists), Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
+ toast.show();
+ return;
+ }
+
+ if (!isSizeValid(fileSizeEditTextValue)) {
+ Toast toast = Toast.makeText(this, getText(R.string.file_size_is_invalid),
+ Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
+ toast.show();
+ return;
+ }
+
+ long fileSize = Integer.valueOf(fileSizeEditTextValue) * mSizeMultiplier;
+
+ if (mFileStorage == FileStorage.EXTERNAL && !Utils.isExternalStorageAvailable()) {
+ Toast toast = Toast.makeText(this,
+ getText(R.string.external_storage_unavailable), Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
+ toast.show();
+ return;
+ }
+
+ createFileWithRandomDataAndFinishActivity(fileName, mFileStorage, String.valueOf(fileSize));
+ }
+
+ private void initFileSizeSpinner() {
+ Spinner spinner = (Spinner) findViewById(R.id.file_size_spinner);
+ final ArrayAdapter<CharSequence> adapter =
+ ArrayAdapter.createFromResource(this, R.array.file_size_array,
+ android.R.layout.simple_spinner_item);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(adapter);
+ spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ String sizeMeasure = adapter.getItem(position).toString();
+ mSizeMultiplier = (int) Math.pow(1024, position);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format("Selected: %s, %d", sizeMeasure,
+ mSizeMultiplier));
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+
+ }
+ });
+ }
+
+ private void initFileStorageSpinner() {
+ Spinner spinner = (Spinner) findViewById(R.id.storage_spinner);
+ final ArrayAdapter<CharSequence> adapter =
+ ArrayAdapter.createFromResource(this, R.array.file_storage_array,
+ android.R.layout.simple_spinner_item);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(adapter);
+ spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ mFileStorage = FileStorage.values()[position];
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+
+ }
+ });
+ }
+
+ private void createFileWithRandomDataAndFinishActivity(String fileName, FileStorage storage,
+ String sizeInBytes) {
+ long size = Long.valueOf(sizeInBytes);
+ File file = null;
+ FileOutputStream out = null;
+ BufferedOutputStream bufOut = null;
+ try {
+ switch (storage) {
+ case INTERNAL:
+ file = getInternalFile(fileName);
+ out = openFileOutput(file.getName(), Context.MODE_PRIVATE);
+ break;
+ case EXTERNAL:
+ assert Utils.isExternalStorageAvailable() :
+ "The external storage is not available";
+ File externalAppDir = getExternalFilesDir(null);
+ file = new File(externalAppDir, fileName);
+ out = new FileOutputStream(file);
+ break;
+ case DONOTBACKUP:
+ file = new File(getNoBackupFilesDir(), fileName);
+ out = new FileOutputStream(file);
+ break;
+ }
+
+ if (file == null || out == null) {
+ Log.d(TAG, "Unable to create file output stream");
+ // Returning back to the caller activity.
+ setResult(MainActivityFragment.ADD_FILE_RESULT_ERROR);
+ finish();
+ return;
+ }
+
+ bufOut = new BufferedOutputStream(out);
+ for (int i = 0; i < size; i++) {
+ byte b = (byte) (255 * Math.random());
+ bufOut.write(b);
+ }
+
+ String message = String.format("File created: %s, size: %s bytes",
+ file.getAbsolutePath(), sizeInBytes);
+
+ Toast toast = Toast.makeText(this, message, Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
+ toast.show();
+ Log.d(TAG, message);
+
+ // Returning back to the caller activity.
+ setResult(MainActivityFragment.ADD_FILE_RESULT_SUCCESS);
+ finish();
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage(), e);
+ // Returning back to the caller activity.
+ setResult(MainActivityFragment.ADD_FILE_RESULT_ERROR);
+ finish();
+ } finally {
+ if (bufOut != null) {
+ try {
+ bufOut.close();
+ } catch (Exception e) {
+ // Ignore.
+ }
+ }
+ }
+ }
+
+ private boolean isFileExists(String fileName) {
+ File file = getInternalFile(fileName);
+ if (file.exists()) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "This file exists: " + file.getName());
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isSizeValid(String sizeInBytesParamValue) {
+ long sizeInBytes = 0;
+ try {
+ sizeInBytes = Long.valueOf(sizeInBytesParamValue);
+ } catch (NumberFormatException e) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Invalid file size: " + sizeInBytesParamValue);
+ }
+ return false;
+ }
+
+ // Validate file size value. It should be 0 or a positive number.
+ if (sizeInBytes < 0) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Invalid file size: " + sizeInBytes);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isFileStorageParamValid(String fileStorage) {
+ try {
+ mFileStorage = FileStorage.valueOf(fileStorage);
+ } catch (IllegalArgumentException e) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Invalid file storage: " + fileStorage);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ private File getInternalFile(String fileName) {
+ File internalAppDir = getFilesDir();
+ return new File(internalAppDir, fileName);
+ }
+
+}
\ No newline at end of file
diff --git a/content/AutomaticBackup/Application/src/main/java/com/example/android/autobackupsample/MainActivity.java b/content/AutomaticBackup/Application/src/main/java/com/example/android/autobackupsample/MainActivity.java
new file mode 100644
index 0000000..b5af289
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/java/com/example/android/autobackupsample/MainActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 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.autobackupsample;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+
+public class MainActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ }
+}
diff --git a/content/AutomaticBackup/Application/src/main/java/com/example/android/autobackupsample/MainActivityFragment.java b/content/AutomaticBackup/Application/src/main/java/com/example/android/autobackupsample/MainActivityFragment.java
new file mode 100644
index 0000000..c07f6b8
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/java/com/example/android/autobackupsample/MainActivityFragment.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2015 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.autobackupsample;
+
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+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.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.io.File;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+
+
+/**
+ * A placeholder fragment containing a simple view.
+ */
+public class MainActivityFragment extends Fragment {
+
+ private static final String TAG = "AutoBackupSample";
+ public static final int ADD_FILE_REQUEST = 1;
+ public static final int ADD_FILE_RESULT_SUCCESS = 101;
+ public static final int ADD_FILE_RESULT_ERROR = 102;
+
+ private ArrayAdapter<File> mFilesArrayAdapter;
+ private ArrayList<File> mFiles;
+
+ public MainActivityFragment() {
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ setHasOptionsMenu(true);
+ return inflater.inflate(R.layout.fragment_main, container, false);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == ADD_FILE_REQUEST && resultCode == ADD_FILE_RESULT_SUCCESS) {
+ updateListOfFiles();
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ inflater.inflate(R.menu.menu_main, menu);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ } else if (id == R.id.action_add_file) {
+ Intent addFileIntent = new Intent(getActivity(), AddFileActivity.class);
+ startActivityForResult(addFileIntent, ADD_FILE_REQUEST);
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mFilesArrayAdapter == null) {
+ mFiles = createListOfFiles();
+ mFilesArrayAdapter = new ArrayAdapter<File>(getActivity(),
+ R.layout.file_list_item, mFiles) {
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ View itemView = inflater.inflate(R.layout.file_list_item, parent, false);
+ TextView fileNameView = (TextView) itemView.findViewById(R.id.file_name);
+ String fileName = getItem(position).getAbsolutePath();
+ fileNameView.setText(fileName);
+ TextView fileSize = (TextView) itemView.findViewById(R.id.file_size);
+ String fileSizeInBytes = NumberFormat.getInstance()
+ .format(getItem(position).length());
+ fileSize.setText(fileSizeInBytes);
+ return itemView;
+ }
+ };
+ updateListOfFiles();
+ ListView filesListView = (ListView) getView().findViewById(R.id.file_list);
+ filesListView.setAdapter(mFilesArrayAdapter);
+ }
+ }
+
+ private ArrayList<File> createListOfFiles() {
+ ArrayList<File> listOfFiles = new ArrayList<File>();
+ addFilesToList(listOfFiles, getActivity().getFilesDir());
+ if (Utils.isExternalStorageAvailable()) {
+ addFilesToList(listOfFiles, getActivity().getExternalFilesDir(null));
+ }
+ addFilesToList(listOfFiles, getActivity().getNoBackupFilesDir());
+ return listOfFiles;
+ }
+
+ private void addFilesToList(ArrayList<File> listOfFiles, File dir) {
+ File[] files = dir.listFiles();
+ for (File file: files) {
+ listOfFiles.add(file);
+ }
+ }
+
+ public void updateListOfFiles() {
+ TextView emptyFileListMessage =
+ (TextView) getView().findViewById(R.id.empty_file_list_message);
+ mFiles = createListOfFiles();
+ if (mFilesArrayAdapter.getCount() > 0) {
+ mFilesArrayAdapter.clear();
+ }
+ for (File file: mFiles) {
+ mFilesArrayAdapter.add(file);
+ }
+ // Display a message instructing to add files if no files found.
+ if (mFiles.size() == 0) {
+ emptyFileListMessage.setVisibility(View.VISIBLE);
+ } else {
+ emptyFileListMessage.setVisibility(View.GONE);
+ }
+ }
+}
+
diff --git a/content/AutomaticBackup/Application/src/main/java/com/example/android/autobackupsample/Utils.java b/content/AutomaticBackup/Application/src/main/java/com/example/android/autobackupsample/Utils.java
new file mode 100644
index 0000000..b9a9833
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/java/com/example/android/autobackupsample/Utils.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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.autobackupsample;
+
+import android.os.Environment;
+import android.util.Log;
+
+/**
+ * Utility methods for the app.
+ */
+public class Utils {
+
+ private static final String TAG = "AutoBackupSample";
+
+ public static boolean isExternalStorageAvailable() {
+ String state = Environment.getExternalStorageState();
+ if (!Environment.MEDIA_MOUNTED.equals(state)) {
+ Log.d(TAG, "The external storage is not available.");
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/content/AutomaticBackup/Application/src/main/res/layout/activity_main.xml b/content/AutomaticBackup/Application/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..4a7879d
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/res/layout/activity_main.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/fragment"
+ android:name="com.example.android.autobackupsample.MainActivityFragment"
+ tools:layout="@layout/fragment_main" android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/content/AutomaticBackup/Application/src/main/res/layout/add_file.xml b/content/AutomaticBackup/Application/src/main/res/layout/add_file.xml
new file mode 100644
index 0000000..9249bf4
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/res/layout/add_file.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal">
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="40dp">
+
+ <!-- File -->
+ <TextView
+ android:id="@+id/file_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="File"
+ android:textAlignment="center"
+ android:textSize="16sp"
+ android:textStyle="bold"
+ android:layout_marginRight="20dp"
+ android:layout_alignBaseline="@+id/file_name"/>
+
+ <EditText
+ android:id="@+id/file_name"
+ android:layout_width="300sp"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@+id/file_label"
+ android:layout_marginBottom="20dp"
+ android:inputType="text"
+ android:text="foo.txt" />
+
+ <!-- Size -->
+ <TextView
+ android:id="@+id/size_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/file_name"
+ android:layout_alignBaseline="@+id/file_size"
+ android:text="Size"
+ android:textSize="16sp"
+ android:textStyle="bold" />
+
+ <EditText
+ android:id="@+id/file_size"
+ android:layout_width="100dp"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/file_name"
+ android:layout_alignStart="@+id/file_name"
+ android:layout_marginBottom="20dp"
+ android:inputType="numberDecimal"
+ android:text="10" />
+
+ <Spinner
+ android:id="@+id/file_size_spinner"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/file_name"
+ android:layout_toRightOf="@+id/file_size"
+ android:layout_alignEnd="@+id/file_name"
+ android:layout_marginBottom="20dp"
+ />
+
+ <!-- Storage -->
+ <TextView
+ android:id="@+id/storage_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/file_size_spinner"
+ android:layout_alignBaseline="@+id/storage_spinner"
+ android:paddingRight="20dp"
+ android:text="Storage"
+ android:textAlignment="center"
+ android:textSize="16sp"
+ android:textStyle="bold" />
+
+ <Spinner
+ android:id="@+id/storage_spinner"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/file_size_spinner"
+ android:layout_toRightOf="@+id/file_size"
+ android:layout_alignEnd="@+id/file_name"/>
+
+ <Button
+ android:id="@+id/create_file_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/storage_spinner"
+ android:layout_centerInParent="true"
+ android:layout_marginTop="40dp"
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:onClick="onCreateFileButtonClick"
+ android:text="Create" />
+
+ </RelativeLayout>
+</LinearLayout>
diff --git a/content/AutomaticBackup/Application/src/main/res/layout/file_list_item.xml b/content/AutomaticBackup/Application/src/main/res/layout/file_list_item.xml
new file mode 100644
index 0000000..e2d1866
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/res/layout/file_list_item.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="20px"
+ android:orientation="horizontal">
+
+ <TextView android:id="@+id/file_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="3"/>
+
+ <TextView android:id="@+id/file_size"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/content/AutomaticBackup/Application/src/main/res/layout/fragment_main.xml b/content/AutomaticBackup/Application/src/main/res/layout/fragment_main.xml
new file mode 100644
index 0000000..35ba962
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/res/layout/fragment_main.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<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:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:orientation="vertical"
+ tools:context=".MainActivity$PlaceholderFragment">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="20sp"
+ android:background="@android:color/darker_gray">
+
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="3"
+ android:textStyle="bold"
+ android:text="File name" />
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textStyle="bold"
+ android:text="Size (bytes)" />
+
+ </LinearLayout>
+
+
+ <ListView android:id="@+id/file_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <TextView android:id="@+id/empty_file_list_message"
+ android:text="@string/no_files_click_add_files_menu"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone" />
+
+</LinearLayout>
diff --git a/content/AutomaticBackup/Application/src/main/res/menu/menu_main.xml b/content/AutomaticBackup/Application/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..ebb08b6
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/res/menu/menu_main.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
+ <item android:id="@+id/action_add_file" android:title="@string/action_add_file"
+ android:orderInCategory="100" android:showAsAction="ifRoom" />
+ <item android:id="@+id/action_settings" android:title="@string/action_settings"
+ android:orderInCategory="100" android:showAsAction="never" />
+</menu>
diff --git a/content/AutomaticBackup/Application/src/main/res/mipmap-hdpi/ic_launcher.png b/content/AutomaticBackup/Application/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100755
index 0000000..91153b2
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/content/AutomaticBackup/Application/src/main/res/mipmap-mdpi/ic_launcher.png b/content/AutomaticBackup/Application/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100755
index 0000000..9a44bc4
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/content/AutomaticBackup/Application/src/main/res/mipmap-xhdpi/ic_launcher.png b/content/AutomaticBackup/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..0e1fb39
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/content/AutomaticBackup/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png b/content/AutomaticBackup/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..e8ae88d
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/content/AutomaticBackup/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/content/AutomaticBackup/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100755
index 0000000..497fde1
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/content/AutomaticBackup/Application/src/main/res/values-w820dp/dimens.xml b/content/AutomaticBackup/Application/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..82d3fbe
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<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/content/AutomaticBackup/Application/src/main/res/values/dimens.xml b/content/AutomaticBackup/Application/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..7d5dcce
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/res/values/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<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/content/AutomaticBackup/Application/src/main/res/values/strings.xml b/content/AutomaticBackup/Application/src/main/res/values/strings.xml
new file mode 100644
index 0000000..30512e8
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/res/values/strings.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+ <string name="action_add_file">Add File</string>
+ <string name="action_settings">Settings</string>
+ <string name="list_of_files">App Data:</string>
+ <string name="file_exists">Please specify a different name. A file with this name exists already.</string>
+ <string name="no_files_click_add_files_menu">Please click on Add File menu in order to create test files.</string>
+ <string name="file_size_is_invalid">Please specify a correct file size.</string>
+ <string name="external_storage_unavailable">The External storage is not available.</string>
+ <string-array name="file_size_array">
+ <item>Bytes</item>
+ <item>Kilobytes</item>
+ <item>Megabytes</item>
+ </string-array>
+ <string-array name="file_storage_array">
+ <item>Internal</item>
+ <item>External</item>
+ <item>Do not Backup</item>
+ </string-array>
+</resources>
diff --git a/content/AutomaticBackup/Application/src/main/res/xml/backup.xml b/content/AutomaticBackup/Application/src/main/res/xml/backup.xml
new file mode 100644
index 0000000..334cfa2
--- /dev/null
+++ b/content/AutomaticBackup/Application/src/main/res/xml/backup.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<full-backup-content xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- This file is referenced from android:fullBackupContent in AndroidManifest.xml, and controls
+ exclusions/inclusions for the default backup policy. -->
+
+ <!-- Shared preferences files can be excluded using the "sharedpref" domain. -->
+ <!-- Be sure to exclude any device-specific identifiers, such as the GCM registration key. -->
+ <!-- You may also wish to exclude directories that contain device-specific session tokens or
+ sensitive user credentials. -->
+ <exclude domain="sharedpref" path="gcm"/>
+ <exclude domain="sharedpref" path="user_credentials"/>
+
+ <!-- Databases can be excluded using the "database" domain. -->
+ <exclude domain="database" path="local_secrets.db"/>
+
+ <!-- Additional domains include "file", "external", "root", and "path". See
+ http://developer.android.com/preview/backup/index.html for more details.
+
+ Additionally, content in the cache directory, external storage, and the no_backup directory
+ (see android.content.Context#getNoBackupFilesDir()) are excluded by default. If you need
+ to backup data in one of these locations, use the <include> directive. -->
+
+</full-backup-content>
\ No newline at end of file
diff --git a/content/AutomaticBackup/build.gradle b/content/AutomaticBackup/build.gradle
new file mode 100644
index 0000000..9b6a9ce
--- /dev/null
+++ b/content/AutomaticBackup/build.gradle
@@ -0,0 +1,12 @@
+
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+ pathToBuild "../../../../build"
+ pathToSamplesCommon "../../common"
+}
+apply from: "../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/content/AutomaticBackup/buildSrc/build.gradle b/content/AutomaticBackup/buildSrc/build.gradle
new file mode 100644
index 0000000..d77115d
--- /dev/null
+++ b/content/AutomaticBackup/buildSrc/build.gradle
@@ -0,0 +1,16 @@
+
+repositories {
+ jcenter()
+}
+dependencies {
+ compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+ main {
+ groovy {
+ srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
+ }
+ }
+}
+
diff --git a/content/AutomaticBackup/gradle/wrapper/gradle-wrapper.jar b/content/AutomaticBackup/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/content/AutomaticBackup/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/content/AutomaticBackup/gradle/wrapper/gradle-wrapper.properties b/content/AutomaticBackup/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..afb3296
--- /dev/null
+++ b/content/AutomaticBackup/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-2.2.1-bin.zip
diff --git a/content/AutomaticBackup/gradlew b/content/AutomaticBackup/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/content/AutomaticBackup/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/content/AutomaticBackup/gradlew.bat b/content/AutomaticBackup/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/content/AutomaticBackup/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/content/AutomaticBackup/screenshots/ic_launcher.png b/content/AutomaticBackup/screenshots/ic_launcher.png
new file mode 100644
index 0000000..b52b886
--- /dev/null
+++ b/content/AutomaticBackup/screenshots/ic_launcher.png
Binary files differ
diff --git a/content/AutomaticBackup/screenshots/main.png b/content/AutomaticBackup/screenshots/main.png
new file mode 100644
index 0000000..aff2276
--- /dev/null
+++ b/content/AutomaticBackup/screenshots/main.png
Binary files differ
diff --git a/content/AutomaticBackup/settings.gradle b/content/AutomaticBackup/settings.gradle
new file mode 100644
index 0000000..0a5c310
--- /dev/null
+++ b/content/AutomaticBackup/settings.gradle
@@ -0,0 +1,2 @@
+
+include 'Application'
diff --git a/content/AutomaticBackup/template-params.xml b/content/AutomaticBackup/template-params.xml
new file mode 100644
index 0000000..fedbf66
--- /dev/null
+++ b/content/AutomaticBackup/template-params.xml
@@ -0,0 +1,83 @@
+<?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.
+-->
+
+<sample>
+ <name>Automatic Backup</name>
+ <group>Content</group>
+ <package>com.example.android.autobackup</package>
+
+ <minSdk>"MNC"</minSdk>
+ <targetSdk>"MNC"</targetSdk>
+ <compileSdkVersion>"android-MNC"</compileSdkVersion>
+
+ <strings>
+ <intro>
+ <![CDATA[
+This sample demonstrates how to selectively disable Automatic Backups in Android M, either by
+adjusting the location where data files are stored using getNoBackupFilesDir(), or by using a custom
+XML configuration file.
+
+This sample can also be used as a utility to test the behavior of the Automatic Backup feature.
+Executing "adb shell bmgr restore com.example.android.autobackup" from a terminal will cause the
+sample\'s data to be cleared and replaced with a copy from the backup server.
+ ]]>
+ </intro>
+ </strings>
+
+ <template src="base" />
+
+ <metadata>
+ <status>PUBLISEHD</status>
+ <categories>Content</categories>
+ <technologies>Android</technologies>
+ <languages>Java</languages>
+ <solutions>Mobile</solutions>
+ <level>INTERMEDIATE</level>
+ <icon>screenshots/icon-web.png</icon>
+ <screenshots>
+ <img>screenshots/1-main.png</img>
+ <img>screenshots/2-settings.png</img>
+ </screenshots>
+ <api_refs>
+ <android>android.app.Backup</android>
+ </api_refs>
+
+ <description>
+ Sample demonstrating how to selectively disable Automatic
+ Backups in Android M, either by adjusting the location where data
+ files are stored or by using a custom XML configuration file.
+ </description>
+
+ <intro>
+ <![CDATA[
+This sample demonstrates how to selectively disable Automatic Backups in Android M, either by
+adjusting the location where data files are stored using [getNoBackupFilesDir()][1], or by using a
+custom XML configuration file.
+
+This sample can also be used as a utility to test the behavior of the Automatic Backup feature.
+Executing:
+
+ adb shell bmgr restorecom.example.android.autobackup
+
+from a terminal will cause the sample's data to be cleared and replaced with a copy from the backup
+server.
+
+[1]: http://developers.google.com/reference/android/content/Context.html#getNoBackupFilesDir()
+ ]]>
+ </intro>
+ </metadata>
+</sample>
diff --git a/media/MediaRecorder/template-params.xml b/media/MediaRecorder/template-params.xml
index fa4a165..a057637 100644
--- a/media/MediaRecorder/template-params.xml
+++ b/media/MediaRecorder/template-params.xml
@@ -18,7 +18,7 @@
<name>MediaRecorder</name>
<group>Media</group>
<package>com.example.android.mediarecorder</package>
- <minSdk>14</minSdk>
+ <minSdk>16</minSdk>
<strings>
<intro>
<![CDATA[
diff --git a/notification/ActiveNotification/Application/.gitignore b/notification/ActiveNotification/Application/.gitignore
new file mode 100644
index 0000000..6eb878d
--- /dev/null
+++ b/notification/ActiveNotification/Application/.gitignore
@@ -0,0 +1,16 @@
+# 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.
+src/template/
+src/common/
+build.gradle
diff --git a/notification/ActiveNotification/Application/README-fragmentview.txt b/notification/ActiveNotification/Application/README-fragmentview.txt
new file mode 100644
index 0000000..38d903f
--- /dev/null
+++ b/notification/ActiveNotification/Application/README-fragmentview.txt
@@ -0,0 +1,37 @@
+<!--
+ 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.
+-->
+
+Steps to implement FragmentView template:
+-in template-params.xml.ftl:
+ -add the following line to common imports
+ <common src="activities"/>
+
+-Add a Fragment to show behavior. In your MainActivity.java class, it will reference a Fragment
+ called (yourProjectName)Fragment.java. Create that file in your project, using the "main" source
+ folder instead of "common" or "templates".
+ For instance, if your package name is com.example.foo, create the file
+ src/main/java/com/example/foo/FooFragment.java
+
+
+-Within this fragment, make sure that the onCreate method has the line
+ "setHasOptionsMenu(true);", to enable the fragment to handle menu events.
+
+-In order to override menu events, override onOptionsItemSelected.
+
+-refer to sampleSamples/fragmentViewSample for a reference implementation of a
+project built on this template.
+
+
diff --git a/notification/ActiveNotification/Application/README-singleview.txt b/notification/ActiveNotification/Application/README-singleview.txt
new file mode 100644
index 0000000..0cacd46
--- /dev/null
+++ b/notification/ActiveNotification/Application/README-singleview.txt
@@ -0,0 +1,47 @@
+<#--
+ 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.
+-->
+
+Steps to implement SingleView template:
+-in template-params.xml.ftl:
+ -add the following line to common imports
+ <common src="activities"/>
+
+ -add a string for the action button's text using the element name "sample_action".
+ This element should be a child of <strings>:
+ <strings>
+ ...
+ <sample_action>ButtonText</sample_action>
+ ...
+ </strings>
+
+
+
+-Add a Fragment to handle behavior. In your MainActivity.java class, it will reference a Fragment
+ called (yourProjectName)Fragment.java. Create that file in your project, using the "main" source
+ folder instead of "common" or "templates".
+ For instance, if your package name is com.example.foo, create the file
+ src/main/java/com/example/foo/FooFragment.java
+
+
+-Within this fragment, make sure that the onCreate method has the line
+ "setHasOptionsMenu(true);", to enable the fragment to handle menu events.
+
+-In order to override menu events, override onOptionsItemSelected.
+
+-refer to sampleSamples/singleViewSample for a reference implementation of a
+project built on this template.
+
+
diff --git a/notification/ActiveNotification/Application/proguard-project.txt b/notification/ActiveNotification/Application/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/notification/ActiveNotification/Application/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/notification/ActiveNotification/Application/src/androidTest/java/com/example/android/activenotification/test/SampleTests.java b/notification/ActiveNotification/Application/src/androidTest/java/com/example/android/activenotification/test/SampleTests.java
new file mode 100644
index 0000000..f23b7c5
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/androidTest/java/com/example/android/activenotification/test/SampleTests.java
@@ -0,0 +1,76 @@
+/*
+* 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) 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.activenotification.test;
+
+import com.example.android.activenotification.*;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+/**
+* Tests for ActiveNotification sample.
+*/
+public class SampleTests extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private MainActivity mTestActivity;
+ private ActiveNotificationFragment 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 = (ActiveNotificationFragment)
+ 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.
+ */
+
+}
diff --git a/notification/ActiveNotification/Application/src/main/AndroidManifest.xml b/notification/ActiveNotification/Application/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a7110ea
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/AndroidManifest.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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.activenotification"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <application android:allowBackup="true"
+ android:label="@string/app_name"
+ android:icon="@mipmap/ic_launcher"
+ android:theme="@style/AppTheme">
+
+ <activity android:name=".ActiveNotificationActivity"
+ 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/notification/ActiveNotification/Application/src/main/java/com/example/android/activenotification/ActiveNotificationActivity.java b/notification/ActiveNotification/Application/src/main/java/com/example/android/activenotification/ActiveNotificationActivity.java
new file mode 100644
index 0000000..7ac7c8c
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/java/com/example/android/activenotification/ActiveNotificationActivity.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015 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.activenotification;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+public class ActiveNotificationActivity extends MainActivity {
+
+ private ActiveNotificationFragment mFragment;
+
+ protected static final String ACTION_NOTIFICATION_DELETE
+ = "com.example.android.activenotification.delete";
+
+ private BroadcastReceiver mDeleteReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mFragment == null) {
+ findFragment();
+ }
+ mFragment.updateNumberOfNotifications();
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
+ super.onCreate(savedInstanceState, persistentState);
+ findFragment();
+ mFragment.updateNumberOfNotifications();
+ }
+
+ private void findFragment() {
+ mFragment = (ActiveNotificationFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.sample_content_fragment);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ registerReceiver(mDeleteReceiver, new IntentFilter(ACTION_NOTIFICATION_DELETE));
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ unregisterReceiver(mDeleteReceiver);
+ }
+}
diff --git a/notification/ActiveNotification/Application/src/main/java/com/example/android/activenotification/ActiveNotificationFragment.java b/notification/ActiveNotification/Application/src/main/java/com/example/android/activenotification/ActiveNotificationFragment.java
new file mode 100644
index 0000000..0f4fbf1
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/java/com/example/android/activenotification/ActiveNotificationFragment.java
@@ -0,0 +1,124 @@
+package com.example.android.activenotification;
+/*
+* Copyright 2015 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.
+*/
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.service.notification.StatusBarNotification;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.example.android.common.logger.Log;
+
+/**
+ * A fragment that enables display of notifications.
+ */
+public class ActiveNotificationFragment extends Fragment {
+
+ private static final String TAG = "ActiveNotificationFragment";
+
+ private NotificationManager mNotificationManager;
+ private TextView mNumberOfNotifications;
+
+ // Every notification needs a unique ID otherwise the previous one would be overwritten.
+ private int mNotificationId = 0;
+ private PendingIntent mDeletePendingIntent;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_notification_builder, container, false);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateNumberOfNotifications();
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ mNotificationManager = (NotificationManager) getActivity().getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ mNumberOfNotifications = (TextView) view.findViewById(R.id.number_of_notifications);
+
+ // Supply actions to the button that is displayed on screen.
+ View.OnClickListener onClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.add_notification: {
+ addNotificationAndReadNumber();
+ break;
+ }
+ }
+ }
+ };
+ view.findViewById(R.id.add_notification).setOnClickListener(onClickListener);
+
+ // [BEGIN create_pending_intent_for_deletion]
+ // Create a PendingIntent to be fired upon deletion of a Notification.
+ Intent deleteIntent = new Intent(ActiveNotificationActivity.ACTION_NOTIFICATION_DELETE);
+ mDeletePendingIntent = PendingIntent.getBroadcast(getActivity(),
+ 2323 /* requestCode */, deleteIntent, 0);
+ // [END create_pending_intent_for_deletion]
+ }
+
+ /**
+ * Add a new {@link Notification} with sample data and send it to the system.
+ * Then read the current number of displayed notifications for this application.
+ */
+ private void addNotificationAndReadNumber() {
+ // [BEGIN create_notification]
+ // Create a Notification and notify the system.
+ final Notification.Builder builder = new Notification.Builder(getActivity())
+ .setSmallIcon(R.mipmap.ic_notification)
+ .setContentTitle(getString(R.string.app_name))
+ .setContentText(getString(R.string.sample_notification_content))
+ .setAutoCancel(true)
+ .setDeleteIntent(mDeletePendingIntent);
+
+ final Notification notification = builder.build();
+ mNotificationManager.notify(++mNotificationId, notification);
+ // [END create_notification]
+ Log.i(TAG, "Add a notification");
+ updateNumberOfNotifications();
+ }
+
+ /**
+ * Request the current number of notifications from the {@link NotificationManager} and
+ * display them to the user.
+ */
+ protected void updateNumberOfNotifications() {
+ // [BEGIN get_active_notifications]
+ // Query the currently displayed notifications.
+ final StatusBarNotification[] activeNotifications = mNotificationManager
+ .getActiveNotifications();
+ // [END get_active_notifications]
+ final int numberOfNotifications = activeNotifications.length;
+ mNumberOfNotifications.setText(getString(R.string.active_notifications,
+ numberOfNotifications));
+ Log.i(TAG, getString(R.string.active_notifications, numberOfNotifications));
+ }
+}
diff --git a/notification/ActiveNotification/Application/src/main/res/layout/fragment_notification_builder.xml b/notification/ActiveNotification/Application/src/main/res/layout/fragment_notification_builder.xml
new file mode 100644
index 0000000..379cd5e
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/res/layout/fragment_notification_builder.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2015 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">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <Button
+ android:id="@+id/add_notification"
+ android:text="@string/add_a_notification"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <TextView
+ style="@android:style/TextAppearance.Material.Large"
+ android:id="@+id/number_of_notifications"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/notification/ActiveNotification/Application/src/main/res/mipmap-hdpi/ic_launcher.png b/notification/ActiveNotification/Application/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..b176e69
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/notification/ActiveNotification/Application/src/main/res/mipmap-hdpi/ic_notification.png b/notification/ActiveNotification/Application/src/main/res/mipmap-hdpi/ic_notification.png
new file mode 100644
index 0000000..efb231d
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/res/mipmap-hdpi/ic_notification.png
Binary files differ
diff --git a/notification/ActiveNotification/Application/src/main/res/mipmap-mdpi/ic_launcher.png b/notification/ActiveNotification/Application/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..205654f
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/notification/ActiveNotification/Application/src/main/res/mipmap-mdpi/ic_notification.png b/notification/ActiveNotification/Application/src/main/res/mipmap-mdpi/ic_notification.png
new file mode 100644
index 0000000..b5f1bc9
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/res/mipmap-mdpi/ic_notification.png
Binary files differ
diff --git a/notification/ActiveNotification/Application/src/main/res/mipmap-xhdpi/ic_launcher.png b/notification/ActiveNotification/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..98262ee
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/notification/ActiveNotification/Application/src/main/res/mipmap-xhdpi/ic_notification.png b/notification/ActiveNotification/Application/src/main/res/mipmap-xhdpi/ic_notification.png
new file mode 100644
index 0000000..3361f2d
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/res/mipmap-xhdpi/ic_notification.png
Binary files differ
diff --git a/notification/ActiveNotification/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png b/notification/ActiveNotification/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..325875a
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/notification/ActiveNotification/Application/src/main/res/mipmap-xxhdpi/ic_notification.png b/notification/ActiveNotification/Application/src/main/res/mipmap-xxhdpi/ic_notification.png
new file mode 100644
index 0000000..7d1946a
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/res/mipmap-xxhdpi/ic_notification.png
Binary files differ
diff --git a/notification/ActiveNotification/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/notification/ActiveNotification/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..254c775
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/notification/ActiveNotification/Application/src/main/res/mipmap-xxxhdpi/ic_notification.png b/notification/ActiveNotification/Application/src/main/res/mipmap-xxxhdpi/ic_notification.png
new file mode 100644
index 0000000..47b12c9
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/res/mipmap-xxxhdpi/ic_notification.png
Binary files differ
diff --git a/notification/ActiveNotification/Application/src/main/res/values/strings.xml b/notification/ActiveNotification/Application/src/main/res/values/strings.xml
new file mode 100644
index 0000000..0f2977d
--- /dev/null
+++ b/notification/ActiveNotification/Application/src/main/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?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="add_a_notification">Add a notification</string>
+ <string name="active_notifications">Active Notifications: %1$s</string>
+ <string name="update_notification_count">Update count</string>
+ <string name="sample_notification_content">This is a sample notification.</string>
+</resources>
diff --git a/notification/ActiveNotification/build.gradle b/notification/ActiveNotification/build.gradle
new file mode 100644
index 0000000..2b8d1ef
--- /dev/null
+++ b/notification/ActiveNotification/build.gradle
@@ -0,0 +1,11 @@
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+ pathToBuild "../../../../build"
+ pathToSamplesCommon "../../common"
+}
+apply from: "../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/notification/ActiveNotification/buildSrc/build.gradle b/notification/ActiveNotification/buildSrc/build.gradle
new file mode 100644
index 0000000..d33a309
--- /dev/null
+++ b/notification/ActiveNotification/buildSrc/build.gradle
@@ -0,0 +1,15 @@
+repositories {
+ jcenter()
+}
+dependencies {
+ compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+ main {
+ groovy {
+ srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
+ }
+ }
+}
+
diff --git a/notification/ActiveNotification/gradle/wrapper/gradle-wrapper.jar b/notification/ActiveNotification/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/notification/ActiveNotification/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/notification/ActiveNotification/gradle/wrapper/gradle-wrapper.properties b/notification/ActiveNotification/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..afb3296
--- /dev/null
+++ b/notification/ActiveNotification/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-2.2.1-bin.zip
diff --git a/notification/ActiveNotification/gradlew b/notification/ActiveNotification/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/notification/ActiveNotification/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/notification/ActiveNotification/gradlew.bat b/notification/ActiveNotification/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/notification/ActiveNotification/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/notification/ActiveNotification/screenshots/icon_web.png b/notification/ActiveNotification/screenshots/icon_web.png
new file mode 100644
index 0000000..063813b
--- /dev/null
+++ b/notification/ActiveNotification/screenshots/icon_web.png
Binary files differ
diff --git a/notification/ActiveNotification/screenshots/screenshot01.png b/notification/ActiveNotification/screenshots/screenshot01.png
new file mode 100644
index 0000000..f37e97f
--- /dev/null
+++ b/notification/ActiveNotification/screenshots/screenshot01.png
Binary files differ
diff --git a/notification/ActiveNotification/screenshots/screenshot02.png b/notification/ActiveNotification/screenshots/screenshot02.png
new file mode 100644
index 0000000..46c3408
--- /dev/null
+++ b/notification/ActiveNotification/screenshots/screenshot02.png
Binary files differ
diff --git a/notification/ActiveNotification/screenshots/screenshot03.png b/notification/ActiveNotification/screenshots/screenshot03.png
new file mode 100644
index 0000000..702f299
--- /dev/null
+++ b/notification/ActiveNotification/screenshots/screenshot03.png
Binary files differ
diff --git a/notification/ActiveNotification/settings.gradle b/notification/ActiveNotification/settings.gradle
new file mode 100644
index 0000000..9464a35
--- /dev/null
+++ b/notification/ActiveNotification/settings.gradle
@@ -0,0 +1 @@
+include 'Application'
diff --git a/notification/ActiveNotification/template-params.xml b/notification/ActiveNotification/template-params.xml
new file mode 100644
index 0000000..2ebebbe
--- /dev/null
+++ b/notification/ActiveNotification/template-params.xml
@@ -0,0 +1,101 @@
+<?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.
+-->
+<sample>
+ <name>ActiveNotification</name>
+ <group>Notification</group> <!-- This field will be deprecated in the future
+ and replaced with the "categories" tags below. -->
+ <package>com.example.android.activenotification</package>
+
+ <!-- change minSdk if needed-->
+ <minSdk>"android-MNC"</minSdk>
+ <compileSdkVersion>"android-MNC"</compileSdkVersion>
+
+ <!-- Include additional dependencies here.-->
+ <!-- dependency>com.google.android.gms:play-services:5.0.+</dependency -->
+
+ <strings>
+ <intro>
+ <![CDATA[
+The NotificationManager can tell you how many notifications your application is currently showing.
+This sample demonstrates how to use this API that has been introduced with Android M.
+To get started, press the "add a notification" button.
+When a notification is being canceled, the count gets updated via a PendingIntent.
+ ]]>
+ </intro>
+ </strings>
+
+ <!-- The basic templates have already been enabled. Uncomment more as desired. -->
+ <template src="base" />
+ <template src="FragmentView" />
+ <common src="activities" />
+ <common src="logger" />
+
+ <metadata>
+ <!-- Values: {DRAFT | PUBLISHED | INTERNAL | DEPRECATED | SUPERCEDED} -->
+ <status>DRAFT</status>
+ <!-- See http://go/sample-categories for details on the next 4 fields. -->
+ <!-- Most samples just need to udpate the Categories field. This is a comma-
+ seperated list of topic tags. Unlike the old category system, samples
+ may have multiple categories, so feel free to add extras. Try to avoid
+ simply tagging everything with "UI". :)-->
+ <categories>Getting Started, Notification</categories>
+ <technologies>Android</technologies>
+ <languages>Java</languages>
+ <solutions>Mobile</solutions>
+ <!-- Values: {BEGINNER | INTERMEDIATE | ADVANCED | EXPERT} -->
+ <!-- Beginner is for "getting started" type content, or essential content.
+ (e.g. "Hello World", activities, intents)
+
+ Intermediate is for content that covers material a beginner doesn't need
+ to know, but that a skilled developer is expected to know.
+ (e.g. services, basic styles and theming, sync adapters)
+
+ Advanced is for highly technical content geared towards experienced developers.
+ (e.g. performance optimizations, custom views, bluetooth)
+
+ Expert is reserved for highly technical or specialized content, and should
+ be used sparingly. (e.g. VPN clients, SELinux, custom instrumentation runners) -->
+ <level>INTERMEDIATE</level>
+ <icon>screenshots/icon-web.png</icon>
+ <screenshots>
+ <img>screenshots/screenshot01.png</img>
+ <img>screenshots/screenshot02.png</img>
+ <img>screenshots/screenshot03.png</img>
+ </screenshots>
+ <api_refs>
+ <android>android.app.NotificationManager</android>
+ </api_refs>
+ <description>
+ The NotificationManager can tell you how many notifications your application is
+ currently showing. This sample demonstrates how to use this API that has been
+ introduced with Android M.
+ </description>
+ <intro>
+ The [NotificationManager][1] has become more powerful.
+ Starting with Android M, you can query it for the active notifications that
+ your app sent using the [notify][2] methods.
+
+ This sample demonstrates simple use of this newly added functionality by
+ allowing a user to add notifications and then querying how many notifications
+ are currently being displayed via the [getActiveNotifications()][3] method.
+
+ [1]: https://developer.android.com/reference/android/app/NotificationManager.html
+ [2]: https://developer.android.com/reference/android/app/NotificationManager.html#notify(int, android.app.Notification)
+ [2]: https://developer.android.com/reference/android/app/NotificationManager.html#getActiveNotifications()
+ </intro>
+ </metadata>
+</sample>
diff --git a/notification/CustomNotifications/template-params.xml b/notification/CustomNotifications/template-params.xml
index b18b927..0c6d951 100644
--- a/notification/CustomNotifications/template-params.xml
+++ b/notification/CustomNotifications/template-params.xml
@@ -20,7 +20,7 @@
<package>com.example.android.customnotifications</package>
<!-- change minSdk if needed-->
- <minSdk>4</minSdk>
+ <minSdk>16</minSdk>
<strings>
<intro>
diff --git a/renderScript/BasicRenderScript/template-params.xml b/renderScript/BasicRenderScript/template-params.xml
index d0d3be2..9f0c558 100644
--- a/renderScript/BasicRenderScript/template-params.xml
+++ b/renderScript/BasicRenderScript/template-params.xml
@@ -23,7 +23,7 @@
<package>com.example.android.basicrenderscript</package>
<!-- change minSdk if needed-->
- <minSdk>8</minSdk>
+ <minSdk>16</minSdk>
<dependency_external>'renderscript-v8.jar'</dependency_external>
diff --git a/renderScript/RenderScriptIntrinsic/template-params.xml b/renderScript/RenderScriptIntrinsic/template-params.xml
index 8b80bb2..a2a9370 100644
--- a/renderScript/RenderScriptIntrinsic/template-params.xml
+++ b/renderScript/RenderScriptIntrinsic/template-params.xml
@@ -21,7 +21,7 @@
<package>com.example.android.renderscriptintrinsic</package>
<!-- change minSdk if needed-->
- <minSdk>8</minSdk>
+ <minSdk>13</minSdk>
<dependency_external>'renderscript-v8.jar'</dependency_external>
diff --git a/security/ConfirmCredential/Application/.gitignore b/security/ConfirmCredential/Application/.gitignore
new file mode 100644
index 0000000..6eb878d
--- /dev/null
+++ b/security/ConfirmCredential/Application/.gitignore
@@ -0,0 +1,16 @@
+# 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.
+src/template/
+src/common/
+build.gradle
diff --git a/security/ConfirmCredential/Application/AndroidManifest.xml b/security/ConfirmCredential/Application/AndroidManifest.xml
new file mode 100644
index 0000000..8f8fbbc
--- /dev/null
+++ b/security/ConfirmCredential/Application/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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.confirmcredential" >
+
+ <uses-permission android:name="android.permission.VIBRATE" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/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/security/ConfirmCredential/Application/README-singleview.txt b/security/ConfirmCredential/Application/README-singleview.txt
new file mode 100644
index 0000000..0cacd46
--- /dev/null
+++ b/security/ConfirmCredential/Application/README-singleview.txt
@@ -0,0 +1,47 @@
+<#--
+ 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.
+-->
+
+Steps to implement SingleView template:
+-in template-params.xml.ftl:
+ -add the following line to common imports
+ <common src="activities"/>
+
+ -add a string for the action button's text using the element name "sample_action".
+ This element should be a child of <strings>:
+ <strings>
+ ...
+ <sample_action>ButtonText</sample_action>
+ ...
+ </strings>
+
+
+
+-Add a Fragment to handle behavior. In your MainActivity.java class, it will reference a Fragment
+ called (yourProjectName)Fragment.java. Create that file in your project, using the "main" source
+ folder instead of "common" or "templates".
+ For instance, if your package name is com.example.foo, create the file
+ src/main/java/com/example/foo/FooFragment.java
+
+
+-Within this fragment, make sure that the onCreate method has the line
+ "setHasOptionsMenu(true);", to enable the fragment to handle menu events.
+
+-In order to override menu events, override onOptionsItemSelected.
+
+-refer to sampleSamples/singleViewSample for a reference implementation of a
+project built on this template.
+
+
diff --git a/security/ConfirmCredential/Application/proguard-project.txt b/security/ConfirmCredential/Application/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/security/ConfirmCredential/Application/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/security/ConfirmCredential/Application/src/main/AndroidManifest.xml b/security/ConfirmCredential/Application/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..12f9456
--- /dev/null
+++ b/security/ConfirmCredential/Application/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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.confirmcredential" >
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/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/security/ConfirmCredential/Application/src/main/java/com/example/android/confirmcredential/MainActivity.java b/security/ConfirmCredential/Application/src/main/java/com/example/android/confirmcredential/MainActivity.java
new file mode 100644
index 0000000..b8cf6e1
--- /dev/null
+++ b/security/ConfirmCredential/Application/src/main/java/com/example/android/confirmcredential/MainActivity.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2015 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.confirmcredential;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.UserNotAuthenticatedException;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+/**
+ * Main entry point for the sample, showing a backpack and "Purchase" button.
+ */
+public class MainActivity extends Activity {
+
+ /** Alias for our key in the Android Key Store. */
+ private static final String KEY_NAME = "my_key";
+ private static final byte[] SECRET_BYTE_ARRAY = new byte[] {1, 2, 3, 4, 5, 6};
+
+ private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 1;
+
+ /**
+ * If the user has unlocked the device Within the last this number of seconds,
+ * it can be considered as an authenticator.
+ */
+ private static final int AUTHENTICATION_DURATION_SECONDS = 30;
+
+ private KeyguardManager mKeyguardManager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
+ Button purchaseButton = (Button) findViewById(R.id.purchase_button);
+ if (!mKeyguardManager.isKeyguardSecure()) {
+ // Show a message that the user hasn't set up a lock screen.
+ Toast.makeText(this,
+ "Secure lock screen hasn't set up.\n"
+ + "Go to 'Settings -> Security -> Screenlock' to set up a lock screen",
+ Toast.LENGTH_LONG).show();
+ purchaseButton.setEnabled(false);
+ return;
+ }
+ createKey();
+ findViewById(R.id.purchase_button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Test to encrypt something. It might fail if the timeout expired (30s).
+ tryEncrypt();
+ }
+ });
+ }
+
+ /**
+ * Tries to encrypt some data with the generated key in {@link #createKey} which is
+ * only works if the user has just authenticated via device credentials.
+ */
+ private void tryEncrypt() {
+ try {
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null);
+ Cipher cipher = Cipher.getInstance(
+ KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/"
+ + KeyProperties.ENCRYPTION_PADDING_PKCS7);
+
+ // Try encrypting something, it will only work if the user authenticated within
+ // the last AUTHENTICATION_DURATION_SECONDS seconds.
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+ cipher.doFinal(SECRET_BYTE_ARRAY);
+
+ // If the user has recently authenticated, you will reach here.
+ showAlreadyAuthenticated();
+ } catch (UserNotAuthenticatedException e) {
+ // User is not authenticated, let's authenticate with device credentials.
+ showAuthenticationScreen();
+ } catch (KeyPermanentlyInvalidatedException e) {
+ // This happens if the lock screen has been disabled or reset after the key was
+ // generated after the key was generated.
+ Toast.makeText(this, "Keys are invalidated after created. Retry the purchase\n"
+ + e.getMessage(),
+ Toast.LENGTH_LONG).show();
+ } catch (BadPaddingException | IllegalBlockSizeException | KeyStoreException |
+ CertificateException | UnrecoverableKeyException | IOException
+ | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Creates a symmetric key in the Android Key Store which can only be used after the user has
+ * authenticated with device credentials within the last X seconds.
+ */
+ private void createKey() {
+ // Generate a key to decrypt payment credentials, tokens, etc.
+ // This will most likely be a registration step for the user when they are setting up your app.
+ try {
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(
+ KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
+
+ // Set the alias of the entry in Android KeyStore where the key will appear
+ // and the constrains (purposes) in the constructor of the Builder
+ keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+ .setUserAuthenticationRequired(true)
+ // Require that the user has unlocked in the last 30 seconds
+ .setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
+ .build());
+ keyGenerator.generateKey();
+ } catch (NoSuchAlgorithmException | NoSuchProviderException
+ | InvalidAlgorithmParameterException | KeyStoreException
+ | CertificateException | IOException e) {
+ throw new RuntimeException("Failed to create a symmetric key", e);
+ }
+ }
+
+ private void showAuthenticationScreen() {
+ // Create the Confirm Credentials screen. You can customize the title and description. Or
+ // we will provide a generic one for you if you leave it null
+ Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null);
+ if (intent != null) {
+ startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) {
+ // Challenge completed, proceed with using cipher
+ if (resultCode == RESULT_OK) {
+ showPurchaseConfirmation();
+ } else {
+ // The user canceled or didn’t complete the lock screen
+ // operation. Go to error/cancellation flow.
+ }
+ }
+ }
+
+ private void showPurchaseConfirmation() {
+ findViewById(R.id.confirmation_message).setVisibility(View.VISIBLE);
+ findViewById(R.id.purchase_button).setEnabled(false);
+ }
+
+ private void showAlreadyAuthenticated() {
+ TextView textView = (TextView) findViewById(
+ R.id.already_has_valid_device_credential_message);
+ textView.setVisibility(View.VISIBLE);
+ textView.setText(getString(
+ R.string.already_confirmed_device_credentials_within_last_x_seconds,
+ AUTHENTICATION_DURATION_SECONDS));
+ findViewById(R.id.purchase_button).setEnabled(false);
+ }
+
+}
diff --git a/security/ConfirmCredential/Application/src/main/res/drawable-nodpi/android_robot.png b/security/ConfirmCredential/Application/src/main/res/drawable-nodpi/android_robot.png
new file mode 100644
index 0000000..40bf934
--- /dev/null
+++ b/security/ConfirmCredential/Application/src/main/res/drawable-nodpi/android_robot.png
Binary files differ
diff --git a/security/ConfirmCredential/Application/src/main/res/drawable/card.xml b/security/ConfirmCredential/Application/src/main/res/drawable/card.xml
new file mode 100644
index 0000000..691a4c5
--- /dev/null
+++ b/security/ConfirmCredential/Application/src/main/res/drawable/card.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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">
+
+ <solid
+ android:color="#fefefe"/>
+
+ <corners
+ android:radius="2dp" />
+</shape>
\ No newline at end of file
diff --git a/security/ConfirmCredential/Application/src/main/res/layout/activity_main.xml b/security/ConfirmCredential/Application/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..da65a00
--- /dev/null
+++ b/security/ConfirmCredential/Application/src/main/res/layout/activity_main.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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
+ -->
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <ImageView
+ android:layout_width="150dp"
+ android:layout_height="150dp"
+ android:layout_marginTop="32dp"
+ android:layout_marginBottom="32dp"
+ android:layout_gravity="center_horizontal"
+ android:scaleType="fitCenter"
+ android:src="@drawable/android_robot"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="8dp"
+ android:orientation="vertical"
+ android:background="@drawable/card"
+ android:elevation="4dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@android:style/TextAppearance.Material.Headline"
+ android:text="@string/item_title"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@android:style/TextAppearance.Material.Body2"
+ android:textColor="?android:attr/colorAccent"
+ android:text="@string/item_price"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:textAppearance="@android:style/TextAppearance.Material.Body1"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/item_description"/>
+
+ </LinearLayout>
+ <Button style="@android:style/Widget.Material.Button.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="4dp"
+ android:layout_gravity="end"
+ android:textColor="?android:attr/textColorPrimaryInverse"
+ android:text="@string/purchase"
+ android:id="@+id/purchase_button"
+ android:layout_alignParentEnd="true"/>
+
+ <TextView
+ android:id="@+id/confirmation_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:textAppearance="@android:style/TextAppearance.Material.Body2"
+ android:textColor="?android:attr/colorAccent"
+ android:text="@string/purchase_done"
+ android:visibility="gone"/>
+
+ <TextView
+ android:id="@+id/already_has_valid_device_credential_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:textAppearance="@android:style/TextAppearance.Material.Body2"
+ android:textColor="?android:attr/colorAccent"
+ android:text="@string/already_confirmed_device_credentials_within_last_x_seconds"
+ android:visibility="gone"/>
+ </LinearLayout>
+</ScrollView>
diff --git a/security/ConfirmCredential/Application/src/main/res/mipmap-hdpi/ic_launcher.png b/security/ConfirmCredential/Application/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..e9cd9e3
--- /dev/null
+++ b/security/ConfirmCredential/Application/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/security/ConfirmCredential/Application/src/main/res/mipmap-mdpi/ic_launcher.png b/security/ConfirmCredential/Application/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..b6c3e31
--- /dev/null
+++ b/security/ConfirmCredential/Application/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/security/ConfirmCredential/Application/src/main/res/mipmap-xhdpi/ic_launcher.png b/security/ConfirmCredential/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..85a14a0
--- /dev/null
+++ b/security/ConfirmCredential/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/security/ConfirmCredential/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png b/security/ConfirmCredential/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d9d35b0
--- /dev/null
+++ b/security/ConfirmCredential/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/security/ConfirmCredential/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/security/ConfirmCredential/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..a764233
--- /dev/null
+++ b/security/ConfirmCredential/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/security/ConfirmCredential/Application/src/main/res/values/strings.xml b/security/ConfirmCredential/Application/src/main/res/values/strings.xml
new file mode 100644
index 0000000..b6ff9d3
--- /dev/null
+++ b/security/ConfirmCredential/Application/src/main/res/values/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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="purchase">Purchase</string>
+ <string name="item_title">White Mesh Pluto Backpack</string>
+ <string name="item_price">$62.68</string>
+ <string name="item_description">Mesh backpack in white. Black textile trim throughout.</string>
+ <string name="purchase_done">Device credential confirmed.</string>
+ <string name="already_confirmed_device_credentials_within_last_x_seconds">
+ The device credential has been already confirmed within the last %1$s seconds.</string>
+</resources>
diff --git a/security/ConfirmCredential/build.gradle b/security/ConfirmCredential/build.gradle
new file mode 100644
index 0000000..2b8d1ef
--- /dev/null
+++ b/security/ConfirmCredential/build.gradle
@@ -0,0 +1,11 @@
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+ pathToBuild "../../../../build"
+ pathToSamplesCommon "../../common"
+}
+apply from: "../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/security/ConfirmCredential/buildSrc/build.gradle b/security/ConfirmCredential/buildSrc/build.gradle
new file mode 100644
index 0000000..8c294c2
--- /dev/null
+++ b/security/ConfirmCredential/buildSrc/build.gradle
@@ -0,0 +1,15 @@
+repositories {
+ mavenCentral()
+}
+dependencies {
+ compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+ main {
+ groovy {
+ srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
+ }
+ }
+}
+
diff --git a/security/ConfirmCredential/gradle/wrapper/gradle-wrapper.jar b/security/ConfirmCredential/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/security/ConfirmCredential/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/security/ConfirmCredential/gradle/wrapper/gradle-wrapper.properties b/security/ConfirmCredential/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..afb3296
--- /dev/null
+++ b/security/ConfirmCredential/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-2.2.1-bin.zip
diff --git a/security/ConfirmCredential/gradlew b/security/ConfirmCredential/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/security/ConfirmCredential/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/security/ConfirmCredential/gradlew.bat b/security/ConfirmCredential/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/security/ConfirmCredential/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/security/ConfirmCredential/screenshots/1-purchase.png b/security/ConfirmCredential/screenshots/1-purchase.png
new file mode 100644
index 0000000..82a616d
--- /dev/null
+++ b/security/ConfirmCredential/screenshots/1-purchase.png
Binary files differ
diff --git a/security/ConfirmCredential/screenshots/2-show-confirm-credential.png b/security/ConfirmCredential/screenshots/2-show-confirm-credential.png
new file mode 100644
index 0000000..869fcae
--- /dev/null
+++ b/security/ConfirmCredential/screenshots/2-show-confirm-credential.png
Binary files differ
diff --git a/security/ConfirmCredential/screenshots/3-already-authenticated.png b/security/ConfirmCredential/screenshots/3-already-authenticated.png
new file mode 100644
index 0000000..bd55084
--- /dev/null
+++ b/security/ConfirmCredential/screenshots/3-already-authenticated.png
Binary files differ
diff --git a/security/ConfirmCredential/screenshots/big-icon.png b/security/ConfirmCredential/screenshots/big-icon.png
new file mode 100644
index 0000000..7449b96
--- /dev/null
+++ b/security/ConfirmCredential/screenshots/big-icon.png
Binary files differ
diff --git a/security/ConfirmCredential/settings.gradle b/security/ConfirmCredential/settings.gradle
new file mode 100644
index 0000000..9464a35
--- /dev/null
+++ b/security/ConfirmCredential/settings.gradle
@@ -0,0 +1 @@
+include 'Application'
diff --git a/security/ConfirmCredential/template-params.xml b/security/ConfirmCredential/template-params.xml
new file mode 100644
index 0000000..8d34864
--- /dev/null
+++ b/security/ConfirmCredential/template-params.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2015 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.
+-->
+<!-- TODO(thagikura) Add tests for Activity once InstrumentationTests can be run
+ on an emulator or a device.
+ At this moment, due to the different API between the image and the SDK, they can't be launched.
+ E.g. Skipping device 'Nexus 5 - MNC', due to different API preview 'MNC' and 'android-MNC'
+ -->
+<sample>
+ <name>Confirm Credential</name>
+ <group>security</group>
+ <package>com.example.android.confirmcredential</package>
+
+ <!-- change minSdk if needed-->
+ <minSdk>"android-MNC"</minSdk>
+ <compileSdkVersion>"android-MNC"</compileSdkVersion>
+
+ <strings>
+ <intro>
+ <![CDATA[
+This sample demonstrates how you can use device credentials (PIN, Pattern, Password) in your app
+to authenticate the user before they are trying to complete some actions.
+ ]]>
+ </intro>
+ </strings>
+
+ <!-- The basic templates have already been enabled. Uncomment more as desired. -->
+ <template src="base" />
+
+ <metadata>
+ <!-- Values: {DRAFT | PUBLISHED | INTERNAL | DEPRECATED | SUPERCEDED} -->
+ <status>DRAFT</status>
+ <categories>Security</categories>
+ <technologies>Android</technologies>
+ <languages>Java</languages>
+ <solutions>Mobile</solutions>
+ <!-- Values: {BEGINNER | INTERMEDIATE | ADVANCED | EXPERT} -->
+ <level>INTERMEDIATE</level>
+ <!-- Dimensions: 512x512, PNG fomrat -->
+ <icon>screenshots/big-icon.png</icon>
+ <!-- Path to screenshots. Use <img> tags for each. -->
+ <screenshots>
+ <img>screenshots/1-purchase.png</img>
+ <img>screenshots/2-show-confirm-credential.png</img>
+ <img>screenshots/3-already-authenticated.png</img>
+ </screenshots>
+ <api_refs>
+ <android>android.app.KeyguardManager.createConfirmDeviceCredentialIntent</android>
+ <android>android.security.KeyGenParameterSpec</android>
+ <android>java.security.KeyStore</android>
+ <android>javax.crypto.Cipher</android>
+ <android>javax.crypto.KeyGenerator</android>
+ </api_refs>
+
+ <description>
+ <![CDATA[
+A sample that demonstrates how to use device credentials (PIN, Pattern, Password) in your app
+ ]]>
+ </description>
+
+ <intro>
+ <![CDATA[
+This sample demonstrates how you can use device credentials (PIN, Pattern, Password) in your app
+to authenticate the user before they are trying to complete some actions.
+
+First you need to create a symmetric key in the Android Key Store using [KeyGenerator][1]
+which can be only be used after the user has authenticated after the user is authenticated
+with their device credentials and pass [KeyGenParameterSpec][2].
+
+By setting an integer value to the
+[KeyGeneratorSpec.Builder.setUserAuthenticationValidityDurationSeconds][3], you can consider the
+user as authenticated if the user has been authenticated with the device credentials
+within the last x seconds.
+
+Then by calling [KeyguardManager.createConfirmDeviceCredentialIntent][4], you can show a screen
+to confirm device credentials to the user.
+
+[1]: https://developer.android.com/reference/javax/crypto/KeyGenerator.html
+[2]: https://developer.android.com/reference/android/security/KeyGenParameterSpec.html
+[3]: https://developer.android.com/reference/android/security/KeyGenParameterSpec.Builder#setUserAuthenticationValidityDurationSeconds().html
+[4]: https://developer.android.com/reference/android/app/KeyguardManager.createConfirmDeviceCredentialIntent().html
+ ]]>
+ </intro>
+ </metadata>
+</sample>
diff --git a/security/FingerprintDialog/Application/.gitignore b/security/FingerprintDialog/Application/.gitignore
new file mode 100644
index 0000000..6eb878d
--- /dev/null
+++ b/security/FingerprintDialog/Application/.gitignore
@@ -0,0 +1,16 @@
+# 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.
+src/template/
+src/common/
+build.gradle
diff --git a/security/FingerprintDialog/Application/proguard-project.txt b/security/FingerprintDialog/Application/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/security/FingerprintDialog/Application/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/security/FingerprintDialog/Application/src/main/AndroidManifest.xml b/security/FingerprintDialog/Application/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5f754ad
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2015 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.fingerprintdialog"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-permission android:name="android.permission.USE_FINGERPRINT"/>
+
+ <application
+ android:name=".InjectedApplication"
+ android:allowBackup="true"
+ android:label="@string/app_name"
+ android:icon="@mipmap/ic_launcher"
+ 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/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintAuthenticationDialogFragment.java b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintAuthenticationDialogFragment.java
new file mode 100644
index 0000000..57c00de
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintAuthenticationDialogFragment.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2015 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.fingerprintdialog;
+
+import android.app.DialogFragment;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import javax.inject.Inject;
+
+/**
+ * A dialog which uses fingerprint APIs to authenticate the user, and falls back to password
+ * authentication if fingerprint is not available.
+ */
+public class FingerprintAuthenticationDialogFragment extends DialogFragment
+ implements TextView.OnEditorActionListener, FingerprintUiHelper.Callback {
+
+ private Button mCancelButton;
+ private Button mSecondDialogButton;
+ private View mFingerprintContent;
+ private View mBackupContent;
+ private EditText mPassword;
+
+ private Stage mStage = Stage.FINGERPRINT;
+
+ private FingerprintManager.CryptoObject mCryptoObject;
+ private FingerprintUiHelper mFingerprintUiHelper;
+
+ @Inject FingerprintUiHelper.FingerprintUiHelperBuilder mFingerprintUiHelperBuilder;
+ @Inject InputMethodManager mInputMethodManager;
+
+ @Inject
+ public FingerprintAuthenticationDialogFragment() {}
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Do not create a new Fragment when the Activity is re-created such as orientation changes.
+ setRetainInstance(true);
+ setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Material_Light_Dialog);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ getDialog().setTitle(getString(R.string.sign_in));
+ View v = inflater.inflate(R.layout.fingerprint_dialog_container, container, false);
+ mCancelButton = (Button) v.findViewById(R.id.cancel_button);
+ mCancelButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ dismiss();
+ }
+ });
+
+ mSecondDialogButton = (Button) v.findViewById(R.id.second_dialog_button);
+ mSecondDialogButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (mStage == Stage.FINGERPRINT) {
+ goToBackup();
+ } else {
+ verifyPassword();
+ }
+ }
+ });
+ mFingerprintContent = v.findViewById(R.id.fingerprint_container);
+ mBackupContent = v.findViewById(R.id.backup_container);
+ mPassword = (EditText) v.findViewById(R.id.password);
+ mPassword.setOnEditorActionListener(this);
+ mFingerprintUiHelper = mFingerprintUiHelperBuilder.build(
+ (ImageView) v.findViewById(R.id.fingerprint_icon),
+ (TextView) v.findViewById(R.id.fingerprint_status), this);
+ updateStage();
+
+ // If fingerprint authentication is not available, switch immediately to the backup
+ // (password) screen.
+ if (!mFingerprintUiHelper.isFingerprintAuthAvailable()) {
+ goToBackup();
+ }
+ return v;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mStage == Stage.FINGERPRINT) {
+ mFingerprintUiHelper.startListening(mCryptoObject);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mFingerprintUiHelper.stopListening();
+ }
+
+ /**
+ * Sets the crypto object to be passed in when authenticating with fingerprint.
+ */
+ public void setCryptoObject(FingerprintManager.CryptoObject cryptoObject) {
+ mCryptoObject = cryptoObject;
+ }
+
+ /**
+ * Switches to backup (password) screen. This either can happen when fingerprint is not
+ * available or the user chooses to use the password authentication method by pressing the
+ * button. This can also happen when the user had too many fingerprint attempts.
+ */
+ private void goToBackup() {
+ mStage = Stage.PASSWORD;
+ updateStage();
+ mPassword.requestFocus();
+
+ // Show the keyboard.
+ mPassword.postDelayed(mShowKeyboardRunnable, 500);
+
+ // Fingerprint is not used anymore. Stop listening for it.
+ mFingerprintUiHelper.stopListening();
+ }
+
+ /**
+ * Checks whether the current entered password is correct, and dismisses the the dialog and
+ * let's the activity know about the result.
+ */
+ private void verifyPassword() {
+ if (checkPassword(mPassword.getText().toString())) {
+ ((MainActivity) getActivity()).onPurchased(false /* without Fingerprint */);
+ dismiss();
+ } else {
+ // assume the password is always correct.
+ }
+ }
+
+ /**
+ * @return true if {@code password} is correct, false otherwise
+ */
+ private boolean checkPassword(String password) {
+ // Assume the password is always correct.
+ // In the real world situation, the password needs to be verified in the server side.
+ return password.length() > 0;
+ }
+
+ private final Runnable mShowKeyboardRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mInputMethodManager.showSoftInput(mPassword, 0);
+ }
+ };
+
+ private void updateStage() {
+ switch (mStage) {
+ case FINGERPRINT:
+ mCancelButton.setText(R.string.cancel);
+ mSecondDialogButton.setText(R.string.use_password);
+ mFingerprintContent.setVisibility(View.VISIBLE);
+ mBackupContent.setVisibility(View.GONE);
+ break;
+ case PASSWORD:
+ mCancelButton.setText(R.string.cancel);
+ mSecondDialogButton.setText(R.string.ok);
+ mFingerprintContent.setVisibility(View.GONE);
+ mBackupContent.setVisibility(View.VISIBLE);
+ break;
+ }
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_GO) {
+ verifyPassword();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onAuthenticated() {
+ // Callback from FingerprintUiHelper. Let the activity know that authentication was
+ // successful.
+ ((MainActivity) getActivity()).onPurchased(true /* withFingerprint */);
+ dismiss();
+ }
+
+ @Override
+ public void onError() {
+ goToBackup();
+ }
+
+ /**
+ * Enumeration to indicate which authentication method the user is trying to authenticate with.
+ */
+ private enum Stage {
+ FINGERPRINT,
+ PASSWORD
+ }
+}
diff --git a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintModule.java b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintModule.java
new file mode 100644
index 0000000..16d5067
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintModule.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 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.fingerprintdialog;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager;
+import android.view.inputmethod.InputMethodManager;
+
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger module for Fingerprint APIs.
+ */
+@Module(
+ library = true,
+ injects = {MainActivity.class}
+)
+public class FingerprintModule {
+
+ private final Context mContext;
+
+ public FingerprintModule(Context context) {
+ mContext = context;
+ }
+
+ @Provides
+ public Context providesContext() {
+ return mContext;
+ }
+
+ @Provides
+ public FingerprintManager providesFingerprintManager(Context context) {
+ return context.getSystemService(FingerprintManager.class);
+ }
+
+ @Provides
+ public KeyguardManager providesKeyguardManager(Context context) {
+ return context.getSystemService(KeyguardManager.class);
+ }
+
+ @Provides
+ public KeyStore providesKeystore() {
+ try {
+ return KeyStore.getInstance("AndroidKeyStore");
+ } catch (KeyStoreException e) {
+ throw new RuntimeException("Failed to get an instance of KeyStore", e);
+ }
+ }
+
+ @Provides
+ public KeyGenerator providesKeyGenerator() {
+ try {
+ return KeyGenerator.getInstance("AES", "AndroidKeyStore");
+ } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
+ throw new RuntimeException("Failed to get an instance of KeyGenerator", e);
+ }
+ }
+
+ @Provides
+ public Cipher providesCipher(KeyStore keyStore) {
+ try {
+ return Cipher.getInstance("AES/CBC/PKCS7Padding");
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+ throw new RuntimeException("Failed to get an instance of Cipher", e);
+ }
+ }
+
+ @Provides
+ public InputMethodManager providesInputMethodManager(Context context) {
+ return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ }
+}
diff --git a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintUiHelper.java b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintUiHelper.java
new file mode 100644
index 0000000..ab7570c
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintUiHelper.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2015 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.fingerprintdialog;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.CancellationSignal;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import javax.inject.Inject;
+
+/**
+ * Small helper class to manage text/icon around fingerprint authentication UI.
+ */
+public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallback {
+
+ @VisibleForTesting static final long ERROR_TIMEOUT_MILLIS = 1600;
+ @VisibleForTesting static final long SUCCESS_DELAY_MILLIS = 1300;
+
+ private final FingerprintManager mFingerprintManager;
+ private final ImageView mIcon;
+ private final TextView mErrorTextView;
+ private final Callback mCallback;
+ private CancellationSignal mCancellationSignal;
+
+ @VisibleForTesting boolean mSelfCancelled;
+
+ /**
+ * Builder class for {@link FingerprintUiHelper} in which injected fields from Dagger
+ * holds its fields and takes other arguments in the {@link #build} method.
+ */
+ public static class FingerprintUiHelperBuilder {
+ private final FingerprintManager mFingerPrintManager;
+
+ @Inject
+ public FingerprintUiHelperBuilder(FingerprintManager fingerprintManager) {
+ mFingerPrintManager = fingerprintManager;
+ }
+
+ public FingerprintUiHelper build(ImageView icon, TextView errorTextView, Callback callback) {
+ return new FingerprintUiHelper(mFingerPrintManager, icon, errorTextView,
+ callback);
+ }
+ }
+
+ /**
+ * Constructor for {@link FingerprintUiHelper}. This method is expected to be called from
+ * only the {@link FingerprintUiHelperBuilder} class.
+ */
+ private FingerprintUiHelper(FingerprintManager fingerprintManager,
+ ImageView icon, TextView errorTextView, Callback callback) {
+ mFingerprintManager = fingerprintManager;
+ mIcon = icon;
+ mErrorTextView = errorTextView;
+ mCallback = callback;
+ }
+
+ public boolean isFingerprintAuthAvailable() {
+ return mFingerprintManager.isHardwareDetected()
+ && mFingerprintManager.hasEnrolledFingerprints();
+ }
+
+ public void startListening(FingerprintManager.CryptoObject cryptoObject) {
+ if (!isFingerprintAuthAvailable()) {
+ return;
+ }
+ mCancellationSignal = new CancellationSignal();
+ mSelfCancelled = false;
+ mFingerprintManager.authenticate(cryptoObject, mCancellationSignal, this, 0 /* flags */);
+ mIcon.setImageResource(R.drawable.ic_fp_40px);
+ }
+
+ public void stopListening() {
+ if (mCancellationSignal != null) {
+ mSelfCancelled = true;
+ mCancellationSignal.cancel();
+ mCancellationSignal = null;
+ }
+ }
+
+ @Override
+ public void onAuthenticationError(int errMsgId, CharSequence errString) {
+ if (!mSelfCancelled) {
+ showError(errString);
+ mIcon.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onError();
+ }
+ }, ERROR_TIMEOUT_MILLIS);
+ }
+ }
+
+ @Override
+ public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+ showError(helpString);
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ showError(mIcon.getResources().getString(
+ R.string.fingerprint_not_recognized));
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
+ mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
+ mIcon.setImageResource(R.drawable.ic_fingerprint_success);
+ mErrorTextView.setTextColor(
+ mErrorTextView.getResources().getColor(R.color.success_color, null));
+ mErrorTextView.setText(
+ mErrorTextView.getResources().getString(R.string.fingerprint_success));
+ mIcon.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onAuthenticated();
+ }
+ }, SUCCESS_DELAY_MILLIS);
+ }
+
+ private void showError(CharSequence error) {
+ mIcon.setImageResource(R.drawable.ic_fingerprint_error);
+ mErrorTextView.setText(error);
+ mErrorTextView.setTextColor(
+ mErrorTextView.getResources().getColor(R.color.warning_color, null));
+ mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
+ mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS);
+ }
+
+ @VisibleForTesting
+ Runnable mResetErrorTextRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mErrorTextView.setTextColor(
+ mErrorTextView.getResources().getColor(R.color.hint_color, null));
+ mErrorTextView.setText(
+ mErrorTextView.getResources().getString(R.string.fingerprint_hint));
+ mIcon.setImageResource(R.drawable.ic_fp_40px);
+ }
+ };
+
+ public interface Callback {
+
+ void onAuthenticated();
+
+ void onError();
+ }
+}
diff --git a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/InjectedApplication.java b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/InjectedApplication.java
new file mode 100644
index 0000000..b7075a9
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/InjectedApplication.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 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.fingerprintdialog;
+
+import android.app.Application;
+import android.util.Log;
+
+import dagger.ObjectGraph;
+
+/**
+ * The Application class of the sample which holds the ObjectGraph in Dagger and enables
+ * dependency injection.
+ */
+public class InjectedApplication extends Application {
+
+ private static final String TAG = InjectedApplication.class.getSimpleName();
+
+ private ObjectGraph mObjectGraph;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ initObjectGraph(new FingerprintModule(this));
+ }
+
+ /**
+ * Initialize the Dagger module. Passing null or mock modules can be used for testing.
+ *
+ * @param module for Dagger
+ */
+ public void initObjectGraph(Object module) {
+ mObjectGraph = module != null ? ObjectGraph.create(module) : null;
+ }
+
+ public void inject(Object object) {
+ if (mObjectGraph == null) {
+ // This usually happens during tests.
+ Log.i(TAG, "Object graph is not initialized.");
+ return;
+ }
+ mObjectGraph.inject(object);
+ }
+
+}
diff --git a/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java
new file mode 100644
index 0000000..9d09765
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/java/com/example/android/fingerprintdialog/MainActivity.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2015 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.fingerprintdialog;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.pm.PackageManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Bundle;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.KeyProperties;
+import android.util.Base64;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.inject.Inject;
+
+/**
+ * Main entry point for the sample, showing a backpack and "Purchase" button.
+ */
+public class MainActivity extends Activity {
+
+ private static final String TAG = MainActivity.class.getSimpleName();
+
+ private static final String DIALOG_FRAGMENT_TAG = "myFragment";
+ private static final String SECRET_MESSAGE = "Very secret message";
+ /** Alias for our key in the Android Key Store */
+ private static final String KEY_NAME = "my_key";
+
+ @Inject KeyguardManager mKeyguardManager;
+ @Inject FingerprintAuthenticationDialogFragment mFragment;
+ @Inject KeyStore mKeyStore;
+ @Inject KeyGenerator mKeyGenerator;
+ @Inject Cipher mCipher;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ((InjectedApplication) getApplication()).inject(this);
+
+ requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, 0);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) {
+ if (requestCode == 0 && state[0] == PackageManager.PERMISSION_GRANTED) {
+ setContentView(R.layout.activity_main);
+ Button purchaseButton = (Button) findViewById(R.id.purchase_button);
+ if (!mKeyguardManager.isKeyguardSecure()) {
+ // Show a message that the user hasn't set up a fingerprint or lock screen.
+ Toast.makeText(this,
+ "Secure lock screen hasn't set up.\n"
+ + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint",
+ Toast.LENGTH_LONG).show();
+ purchaseButton.setEnabled(false);
+ return;
+ }
+ createKey();
+ purchaseButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ // Show the fingerprint dialog. The user has the option to use the fingerprint with
+ // crypto, or you can fall back to using a server-side verified password.
+ mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
+ mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+ }
+ });
+
+ // Set up the crypto object for later. The object will be authenticated by use
+ // of the fingerprint.
+ initCipher();
+ }
+ }
+
+ private void initCipher() {
+ try {
+ mKeyStore.load(null);
+ SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null);
+ mCipher.init(Cipher.ENCRYPT_MODE, key);
+ } catch (KeyPermanentlyInvalidatedException e) {
+ // This happens if the lock screen has been disabled or reset after the key was
+ // generated, or if a fingerprint got enrolled after the key was generated.
+ Toast.makeText(this, "Keys are invalidated after created. Retry the purchase\n"
+ + e.getMessage(),
+ Toast.LENGTH_LONG).show();
+ } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
+ | NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new RuntimeException("Failed to init Cipher", e);
+ }
+ }
+
+ public void onPurchased(boolean withFingerprint) {
+ findViewById(R.id.purchase_button).setVisibility(View.GONE);
+ if (withFingerprint) {
+ // If the user has authenticated with fingerprint, verify that using cryptography and
+ // then show the confirmation message.
+ tryEncrypt();
+ } else {
+ // Authentication happened with backup password. Just show the confirmation message.
+ showConfirmation(null);
+ }
+ }
+
+ // Show confirmation, if fingerprint was used show crypto information.
+ private void showConfirmation(byte[] encrypted) {
+ findViewById(R.id.confirmation_message).setVisibility(View.VISIBLE);
+ if (encrypted != null) {
+ TextView v = (TextView) findViewById(R.id.encrypted_message);
+ v.setVisibility(View.VISIBLE);
+ v.setText(Base64.encodeToString(encrypted, 0 /* flags */));
+ }
+ }
+
+ /**
+ * Tries to encrypt some data with the generated key in {@link #createKey} which is
+ * only works if the user has just authenticated via fingerprint.
+ */
+ private void tryEncrypt() {
+ try {
+ byte[] encrypted = mCipher.doFinal(SECRET_MESSAGE.getBytes());
+ showConfirmation(encrypted);
+ } catch (BadPaddingException | IllegalBlockSizeException e) {
+ Toast.makeText(this, "Failed to encrypt the data with the generated key. "
+ + "Retry the purchase", Toast.LENGTH_LONG).show();
+ Log.e(TAG, "Failed to encrypt the data with the generated key." + e.getMessage());
+ }
+ }
+
+ /**
+ * Creates a symmetric key in the Android Key Store which can only be used after the user has
+ * authenticated with fingerprint.
+ */
+ private void createKey() {
+ // The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint
+ // for your flow. Use of keys is necessary if you need to know if the set of
+ // enrolled fingerprints has changed.
+ try {
+ mKeyStore.load(null);
+ // Set the alias of the entry in Android KeyStore where the key will appear
+ // and the constrains (purposes) in the constructor of the Builder
+ mKeyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
+ KeyProperties.PURPOSE_ENCRYPT |
+ KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+ // Require the user to authenticate with a fingerprint to authorize every use
+ // of the key
+ .setUserAuthenticationRequired(true)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
+ .build());
+ mKeyGenerator.generateKey();
+ } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
+ | CertificateException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/security/FingerprintDialog/Application/src/main/res/drawable-hdpi/ic_fp_40px.png b/security/FingerprintDialog/Application/src/main/res/drawable-hdpi/ic_fp_40px.png
new file mode 100644
index 0000000..48ebd8a
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/drawable-hdpi/ic_fp_40px.png
Binary files differ
diff --git a/security/FingerprintDialog/Application/src/main/res/drawable-mdpi/ic_fp_40px.png b/security/FingerprintDialog/Application/src/main/res/drawable-mdpi/ic_fp_40px.png
new file mode 100644
index 0000000..122f442
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/drawable-mdpi/ic_fp_40px.png
Binary files differ
diff --git a/security/FingerprintDialog/Application/src/main/res/drawable-nodpi/android_robot.png b/security/FingerprintDialog/Application/src/main/res/drawable-nodpi/android_robot.png
new file mode 100644
index 0000000..40bf934
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/drawable-nodpi/android_robot.png
Binary files differ
diff --git a/security/FingerprintDialog/Application/src/main/res/drawable-xhdpi/ic_fp_40px.png b/security/FingerprintDialog/Application/src/main/res/drawable-xhdpi/ic_fp_40px.png
new file mode 100644
index 0000000..e1c9590
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/drawable-xhdpi/ic_fp_40px.png
Binary files differ
diff --git a/security/FingerprintDialog/Application/src/main/res/drawable-xxhdpi/ic_fp_40px.png b/security/FingerprintDialog/Application/src/main/res/drawable-xxhdpi/ic_fp_40px.png
new file mode 100644
index 0000000..f7e8724
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/drawable-xxhdpi/ic_fp_40px.png
Binary files differ
diff --git a/security/FingerprintDialog/Application/src/main/res/drawable-xxxhdpi/ic_fp_40px.png b/security/FingerprintDialog/Application/src/main/res/drawable-xxxhdpi/ic_fp_40px.png
new file mode 100644
index 0000000..0fb8545
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/drawable-xxxhdpi/ic_fp_40px.png
Binary files differ
diff --git a/security/FingerprintDialog/Application/src/main/res/drawable/card.xml b/security/FingerprintDialog/Application/src/main/res/drawable/card.xml
new file mode 100644
index 0000000..691a4c5
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/drawable/card.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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">
+
+ <solid
+ android:color="#fefefe"/>
+
+ <corners
+ android:radius="2dp" />
+</shape>
\ No newline at end of file
diff --git a/security/FingerprintDialog/Application/src/main/res/drawable/ic_fingerprint_error.xml b/security/FingerprintDialog/Application/src/main/res/drawable/ic_fingerprint_error.xml
new file mode 100644
index 0000000..be46116
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/drawable/ic_fingerprint_error.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40.0dp"
+ android:height="40.0dp"
+ android:viewportWidth="40.0"
+ android:viewportHeight="40.0">
+ <path
+ android:pathData="M20.0,0.0C8.96,0.0 0.0,8.95 0.0,20.0s8.96,20.0 20.0,20.0c11.04,0.0 20.0,-8.95 20.0,-20.0S31.04,0.0 20.0,0.0z"
+ android:fillColor="#F4511E"/>
+ <path
+ android:pathData="M21.33,29.33l-2.67,0.0l0.0,-2.67l2.67,0.0L21.33,29.33zM21.33,22.67l-2.67,0.0l0.0,-12.0l2.67,0.0L21.33,22.67z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/security/FingerprintDialog/Application/src/main/res/drawable/ic_fingerprint_success.xml b/security/FingerprintDialog/Application/src/main/res/drawable/ic_fingerprint_success.xml
new file mode 100644
index 0000000..261f3e7
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/drawable/ic_fingerprint_success.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40.0dp"
+ android:height="40.0dp"
+ android:viewportWidth="40.0"
+ android:viewportHeight="40.0">
+ <path
+ android:pathData="M20.0,20.0m-20.0,0.0a20.0,20.0 0.0,1.0 1.0,40.0 0.0a20.0,20.0 0.0,1.0 1.0,-40.0 0.0"
+ android:fillColor="#009688"/>
+ <path
+ android:pathData="M11.2,21.41l1.63,-1.619999 4.17,4.169998 10.59,-10.589999 1.619999,1.63 -12.209999,12.209999z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml b/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..8f30557
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/layout/activity_main.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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
+ -->
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <ImageView
+ android:layout_width="150dp"
+ android:layout_height="150dp"
+ android:layout_marginTop="32dp"
+ android:layout_marginBottom="32dp"
+ android:layout_gravity="center_horizontal"
+ android:scaleType="fitCenter"
+ android:src="@drawable/android_robot"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="8dp"
+ android:orientation="vertical"
+ android:background="@drawable/card"
+ android:elevation="4dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@android:style/TextAppearance.Material.Headline"
+ android:text="@string/item_title"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@android:style/TextAppearance.Material.Body2"
+ android:textColor="?android:attr/colorAccent"
+ android:text="@string/item_price"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:textAppearance="@android:style/TextAppearance.Material.Body1"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/item_description"/>
+
+ </LinearLayout>
+ <Button style="@android:style/Widget.Material.Button.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="4dp"
+ android:layout_gravity="end"
+ android:textColor="?android:attr/textColorPrimaryInverse"
+ android:text="@string/purchase"
+ android:id="@+id/purchase_button"
+ android:layout_alignParentEnd="true"/>
+
+ <TextView
+ android:id="@+id/confirmation_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:textAppearance="@android:style/TextAppearance.Material.Body2"
+ android:textColor="?android:attr/colorAccent"
+ android:text="@string/purchase_done"
+ android:visibility="gone"/>
+
+ <TextView
+ android:id="@+id/encrypted_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:textAppearance="@android:style/TextAppearance.Material.Body2"
+ android:textColor="?android:attr/colorAccent"
+ android:text="@string/purchase_done"
+ android:visibility="gone"/>
+ </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/security/FingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_backup.xml b/security/FingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_backup.xml
new file mode 100644
index 0000000..0b88e33
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_backup.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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 xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/backup_container"
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+ android:text="@string/password_description"
+ android:id="@+id/description"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentStart="true"
+ android:layout_marginStart="24dp"
+ android:layout_marginEnd="24dp"
+ android:textColor="?android:attr/textColorSecondary"/>
+
+ <EditText
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"
+ android:ems="10"
+ android:hint="@string/password"
+ android:imeOptions="actionGo"
+ android:id="@+id/password"
+ android:layout_below="@+id/description"
+ android:layout_marginTop="16dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="20dp"
+ android:layout_alignParentStart="true" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/security/FingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_container.xml b/security/FingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_container.xml
new file mode 100644
index 0000000..08bb1bb
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_container.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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">
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <include layout="@layout/fingerprint_dialog_content" />
+
+ <include
+ layout="@layout/fingerprint_dialog_backup"
+ android:visibility="gone" />
+
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/buttonPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp"
+ android:gravity="bottom"
+ style="?android:attr/buttonBarStyle">
+
+ <Space
+ android:id="@+id/spacer"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="invisible" />
+ <Button
+ android:id="@+id/cancel_button"
+ style="?android:attr/buttonBarNegativeButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <Button
+ android:id="@+id/second_dialog_button"
+ style="?android:attr/buttonBarPositiveButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/security/FingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_content.xml b/security/FingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_content.xml
new file mode 100644
index 0000000..b56ccbb
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_content.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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 xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fingerprint_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="8dp"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:paddingTop="16dp">
+
+ <TextView
+ android:id="@+id/fingerprint_description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:text="@string/fingerprint_description"
+ android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+ android:textColor="?android:attr/textColorSecondary"/>
+
+
+ <ImageView
+ android:id="@+id/fingerprint_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_below="@+id/fingerprint_description"
+ android:layout_marginTop="20dp"
+ android:src="@drawable/ic_fp_40px" />
+
+ <TextView
+ android:id="@+id/fingerprint_status"
+ style="@android:style/TextAppearance.Material.Body1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignBottom="@+id/fingerprint_icon"
+ android:layout_alignTop="@+id/fingerprint_icon"
+ android:layout_marginLeft="16dp"
+ android:layout_toEndOf="@+id/fingerprint_icon"
+ android:gravity="center_vertical"
+ android:text="@string/fingerprint_hint"
+ android:textColor="@color/hint_color" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/security/FingerprintDialog/Application/src/main/res/mipmap-hdpi/ic_launcher.png b/security/FingerprintDialog/Application/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..68c473a
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/security/FingerprintDialog/Application/src/main/res/mipmap-mdpi/ic_launcher.png b/security/FingerprintDialog/Application/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..fd7e5f6
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/security/FingerprintDialog/Application/src/main/res/mipmap-xhdpi/ic_launcher.png b/security/FingerprintDialog/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..106c0d3
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/security/FingerprintDialog/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png b/security/FingerprintDialog/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b319beb
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/security/FingerprintDialog/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/security/FingerprintDialog/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4dc0ddf
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/security/FingerprintDialog/Application/src/main/res/values/colors.xml b/security/FingerprintDialog/Application/src/main/res/values/colors.xml
new file mode 100644
index 0000000..a24f3c8
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/values/colors.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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="warning_color">#f4511e</color>
+ <color name="hint_color">#42000000</color>
+ <color name="success_color">#009688</color>
+</resources>
diff --git a/security/FingerprintDialog/Application/src/main/res/values/strings.xml b/security/FingerprintDialog/Application/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8a6ecde
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/main/res/values/strings.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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="action_settings">Settings</string>
+ <string name="cancel">Cancel</string>
+ <string name="use_password">Use password</string>
+ <string name="sign_in">Sign in</string>
+ <string name="ok">Ok</string>
+ <string name="password">Password</string>
+ <string name="fingerprint_description">Confirm fingerprint to continue</string>
+ <string name="fingerprint_hint">Touch sensor</string>
+ <string name="password_description">Enter your store password to continue</string>
+ <string name="purchase">Purchase</string>
+ <string name="fingerprint_not_recognized">Fingerprint not recognized. Try again</string>
+ <string name="fingerprint_success">Fingerprint recognized</string>
+ <string name="item_title">White Mesh Pluto Backpack</string>
+ <string name="item_price">$62.68</string>
+ <string name="item_description">Mesh backpack in white. Black textile trim throughout.</string>
+ <string name="purchase_done">Purchase successful</string>
+</resources>
diff --git a/security/FingerprintDialog/Application/src/test/java/com/example/android/fingerprintdialog/FingerprintUiHelperTest.java b/security/FingerprintDialog/Application/src/test/java/com/example/android/fingerprintdialog/FingerprintUiHelperTest.java
new file mode 100644
index 0000000..eeb6d24
--- /dev/null
+++ b/security/FingerprintDialog/Application/src/test/java/com/example/android/fingerprintdialog/FingerprintUiHelperTest.java
@@ -0,0 +1,109 @@
+package com.example.android.fingerprintdialog;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import android.content.res.Resources;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.CancellationSignal;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for {@link FingerprintUiHelper}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class FingerprintUiHelperTest {
+
+ private static final int ERROR_MSG_ID = 1;
+ private static final CharSequence ERR_STRING = "ERROR_STRING";
+ private static final int HINT_COLOR = 10;
+
+ @Mock private FingerprintManager mockFingerprintManager;
+ @Mock private ImageView mockIcon;
+ @Mock private TextView mockTextView;
+ @Mock private FingerprintUiHelper.Callback mockCallback;
+ @Mock private FingerprintManager.CryptoObject mockCryptoObject;
+ @Mock private Resources mockResources;
+
+ @Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
+
+ @InjectMocks private FingerprintUiHelper.FingerprintUiHelperBuilder mockBuilder;
+
+ private FingerprintUiHelper mFingerprintUiHelper;
+
+ @Before
+ public void setUp() {
+ mFingerprintUiHelper = mockBuilder.build(mockIcon, mockTextView, mockCallback);
+
+ when(mockFingerprintManager.isHardwareDetected()).thenReturn(true);
+ when(mockFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
+ when(mockTextView.getResources()).thenReturn(mockResources);
+ when(mockResources.getColor(R.color.hint_color, null)).thenReturn(HINT_COLOR);
+ }
+
+ @Test
+ public void testStartListening_fingerprintAuthAvailable() {
+ mFingerprintUiHelper.startListening(mockCryptoObject);
+
+ verify(mockFingerprintManager).authenticate(eq(mockCryptoObject),
+ isA(CancellationSignal.class), eq(mFingerprintUiHelper), eq(0));
+ verify(mockIcon).setImageResource(R.drawable.ic_fp_40px);
+ }
+
+ @Test
+ public void testStartListening_fingerprintAuthNotAvailable() {
+ when(mockFingerprintManager.isHardwareDetected()).thenReturn(false);
+
+ mFingerprintUiHelper.startListening(mockCryptoObject);
+
+ verify(mockFingerprintManager, never()).authenticate(
+ any(FingerprintManager.CryptoObject.class),
+ any(CancellationSignal.class), any(FingerprintUiHelper.class), eq(0));
+ }
+
+ @Test
+ public void testOnAuthenticationError() {
+ mFingerprintUiHelper.mSelfCancelled = false;
+
+ mFingerprintUiHelper.onAuthenticationError(ERROR_MSG_ID, ERR_STRING);
+
+ verify(mockIcon).setImageResource(R.drawable.ic_fingerprint_error);
+ verify(mockTextView).removeCallbacks(mFingerprintUiHelper.mResetErrorTextRunnable);
+ verify(mockTextView).postDelayed(mFingerprintUiHelper.mResetErrorTextRunnable,
+ FingerprintUiHelper.ERROR_TIMEOUT_MILLIS);
+ verify(mockIcon).postDelayed(mRunnableArgumentCaptor.capture(),
+ eq(FingerprintUiHelper.ERROR_TIMEOUT_MILLIS));
+
+ mRunnableArgumentCaptor.getValue().run();
+
+ verify(mockCallback).onError();
+ }
+
+ @Test
+ public void testOnAuthenticationSucceeded() {
+ mFingerprintUiHelper.onAuthenticationSucceeded(null);
+
+ verify(mockIcon).setImageResource(R.drawable.ic_fingerprint_success);
+ verify(mockTextView).removeCallbacks(mFingerprintUiHelper.mResetErrorTextRunnable);
+ verify(mockIcon).postDelayed(mRunnableArgumentCaptor.capture(),
+ eq(FingerprintUiHelper.SUCCESS_DELAY_MILLIS));
+
+ mRunnableArgumentCaptor.getValue().run();
+
+ verify(mockCallback).onAuthenticated();
+ }
+}
diff --git a/security/FingerprintDialog/build.gradle b/security/FingerprintDialog/build.gradle
new file mode 100644
index 0000000..2b8d1ef
--- /dev/null
+++ b/security/FingerprintDialog/build.gradle
@@ -0,0 +1,11 @@
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+ pathToBuild "../../../../build"
+ pathToSamplesCommon "../../common"
+}
+apply from: "../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/security/FingerprintDialog/buildSrc/build.gradle b/security/FingerprintDialog/buildSrc/build.gradle
new file mode 100644
index 0000000..8c294c2
--- /dev/null
+++ b/security/FingerprintDialog/buildSrc/build.gradle
@@ -0,0 +1,15 @@
+repositories {
+ mavenCentral()
+}
+dependencies {
+ compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+ main {
+ groovy {
+ srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
+ }
+ }
+}
+
diff --git a/security/FingerprintDialog/gradle/wrapper/gradle-wrapper.jar b/security/FingerprintDialog/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/security/FingerprintDialog/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/security/FingerprintDialog/gradle/wrapper/gradle-wrapper.properties b/security/FingerprintDialog/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..f943b1e
--- /dev/null
+++ b/security/FingerprintDialog/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Apr 27 11:28:32 JST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/security/FingerprintDialog/gradlew b/security/FingerprintDialog/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/security/FingerprintDialog/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/security/FingerprintDialog/gradlew.bat b/security/FingerprintDialog/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/security/FingerprintDialog/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/security/FingerprintDialog/screenshots/1-purchase-screen.png b/security/FingerprintDialog/screenshots/1-purchase-screen.png
new file mode 100644
index 0000000..0bf03bd
--- /dev/null
+++ b/security/FingerprintDialog/screenshots/1-purchase-screen.png
Binary files differ
diff --git a/security/FingerprintDialog/screenshots/2-fingerprint-dialog.png b/security/FingerprintDialog/screenshots/2-fingerprint-dialog.png
new file mode 100644
index 0000000..5e681f9
--- /dev/null
+++ b/security/FingerprintDialog/screenshots/2-fingerprint-dialog.png
Binary files differ
diff --git a/security/FingerprintDialog/screenshots/3-fingerprint-authenticated.png b/security/FingerprintDialog/screenshots/3-fingerprint-authenticated.png
new file mode 100644
index 0000000..d485b1d
--- /dev/null
+++ b/security/FingerprintDialog/screenshots/3-fingerprint-authenticated.png
Binary files differ
diff --git a/security/FingerprintDialog/screenshots/big-icon.png b/security/FingerprintDialog/screenshots/big-icon.png
new file mode 100644
index 0000000..9e32334
--- /dev/null
+++ b/security/FingerprintDialog/screenshots/big-icon.png
Binary files differ
diff --git a/security/FingerprintDialog/settings.gradle b/security/FingerprintDialog/settings.gradle
new file mode 100644
index 0000000..9464a35
--- /dev/null
+++ b/security/FingerprintDialog/settings.gradle
@@ -0,0 +1 @@
+include 'Application'
diff --git a/security/FingerprintDialog/template-params.xml b/security/FingerprintDialog/template-params.xml
new file mode 100644
index 0000000..f0f7c4d
--- /dev/null
+++ b/security/FingerprintDialog/template-params.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2015 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.
+-->
+<!-- TODO(thagikura) Add tests for Activity and Fragment once InstrumentationTests can be run
+ on an emulator or a device.
+ At this moment, due to the different API between the image and the SDK, they can't be launched.
+ E.g. Skipping device 'Nexus 5 - MNC', due to different API preview 'MNC' and 'android-MNC'
+ -->
+<sample>
+ <name>Fingerprint Dialog Sample</name>
+ <group>security</group>
+ <package>com.example.android.fingerprintdialog</package>
+
+ <minSdk>"android-MNC"</minSdk>
+ <compileSdkVersion>"android-MNC"</compileSdkVersion>
+
+ <dependency>com.squareup.dagger:dagger:1.2.2</dependency>
+ <dependency>com.squareup.dagger:dagger-compiler:1.2.2</dependency>
+
+ <!-- TODO(thagikura) These dependencies should be created as testCompile instead of compile but
+ the template system doesn't allow androidTestCompile dependencies. Change it once fixed.
+ -->
+ <dependency>junit:junit:4.12</dependency>
+ <dependency>org.mockito:mockito-core:1.10.19</dependency>
+
+ <strings>
+ <intro>
+ <![CDATA[
+This sample demonstrates how you can use registered fingerprints to authenticate the user
+before proceeding some actions such as purchasing an item.
+ ]]>
+ </intro>
+ </strings>
+
+ <template src="base" />
+
+ <metadata>
+ <!-- Values: {DRAFT | PUBLISHED | INTERNAL | DEPRECATED | SUPERCEDED} -->
+ <status>DRAFT</status>
+ <categories>security</categories>
+ <technologies>Android</technologies>
+ <languages>Java</languages>
+ <solutions>Mobile</solutions>
+ <level>INTERMEDIATE</level>
+ <icon>screenshots/big-icon.png</icon>
+ <screenshots>
+ <img>screenshots/1-purchase-screen.png</img>
+ <img>screenshots/2-fingerprint-dialog.png</img>
+ <img>screenshots/3-fingerprint-authenticated.png</img>
+ </screenshots>
+
+ <api_refs>
+ <android>android.hardware.fingerprint.FingerprintManager</android>
+ <android>android.hardware.fingerprint.FingerprintManager.AuthenticationCallback</android>
+ <android>android.hardware.fingerprint.FingerprintManager.CryptoObject</android>
+ <android>android.security.KeyGenParameterSpec</android>
+ <android>java.security.KeyStore</android>
+ <android>javax.crypto.Cipher</android>
+ <android>javax.crypto.KeyGenerator</android>
+ </api_refs>
+
+ <description>
+<![CDATA[
+A sample that demonstrates to use registered fingerprints to authenticate the user in your app
+]]>
+ </description>
+
+ <intro>
+<![CDATA[
+This sample demonstrates how you can use registered fingerprints in your app to authenticate the user
+before proceeding some actions such as purchasing an item.
+
+First you need to create a symmetric key in the Android Key Store using [KeyGenerator][1]
+which can be only be used after the user has authenticated with fingerprint and pass
+a [KeyGeneratorSpec][2].
+
+By setting [KeyGeneratorSpec.Builder.setUserAuthenticationRequired][3] to true, you can permit the
+use of the key only after the user authenticate it including when authenticated with the user's
+fingerprint.
+
+Then start listening to a fingerprint on the fingerprint sensor by calling
+[FingerprintManager.authenticate][4] with a [Cipher][5] initialized with the symmetric key created.
+Or alternatively you can fall back to server-side verified password as an authenticator.
+
+Once the fingerprint (or password) is verified, the
+[FingerprintManager.AuthenticationCallback#onAuthenticationSucceeded()][6] callback is called.
+
+[1]: https://developer.android.com/reference/javax/crypto/KeyGenerator.html
+[2]: https://developer.android.com/reference/android/security/KeyGenParameterSpec.html
+[3]: https://developer.android.com/reference/android/security/KeyGenParameterSpec.Builder#setUserAuthenticationRequired().html
+[4]: https://developer.android.com/reference/android/hardware/FingerprintManager#authenticate().html
+[5]: https://developer.android.com/reference/javax/crypto/Cipher.html
+[6]: https://developer.android.com/reference/android/hardware/FingerprintManager.AuthenticationCallback#onAuthenticationSucceeded().html
+]]>
+ </intro>
+ </metadata>
+</sample>
diff --git a/system/RuntimePermissions/Application/.gitignore b/system/RuntimePermissions/Application/.gitignore
new file mode 100644
index 0000000..6eb878d
--- /dev/null
+++ b/system/RuntimePermissions/Application/.gitignore
@@ -0,0 +1,16 @@
+# 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.
+src/template/
+src/common/
+build.gradle
diff --git a/system/RuntimePermissions/Application/proguard-project.txt b/system/RuntimePermissions/Application/proguard-project.txt
new file mode 100644
index 0000000..0d8f171
--- /dev/null
+++ b/system/RuntimePermissions/Application/proguard-project.txt
@@ -0,0 +1,20 @@
+ To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/system/RuntimePermissions/Application/src/main/AndroidManifest.xml b/system/RuntimePermissions/Application/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..050b051
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015 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.system.runtimepermissions" >
+
+ <!-- BEGIN_INCLUDE(manifest) -->
+
+ <!-- Note that all required permissions are declared here in the Android manifest.
+ On Android M and above, use of these permissions is only requested at run time. -->
+ <uses-permission android:name="android.permission.CAMERA"/>
+
+ <!-- The following permissions are only requested if the device is on M or above.
+ On older platforms these permissions are not requested and will not be available. -->
+ <uses-permission-sdk-m android:name="android.permission.READ_CONTACTS" />
+ <uses-permission-sdk-m android:name="android.permission.WRITE_CONTACTS" />
+
+ <!-- END_INCLUDE(manifest) -->
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/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/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/MainActivity.java b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/MainActivity.java
new file mode 100644
index 0000000..43436aa
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/MainActivity.java
@@ -0,0 +1,279 @@
+/*
+* Copyright 2015 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.system.runtimepermissions;
+
+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;
+import com.example.android.system.runtimepermissions.camera.CameraPreviewFragment;
+import com.example.android.system.runtimepermissions.contacts.ContactsFragment;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.support.v4.app.FragmentTransaction;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Toast;
+import android.widget.ViewAnimator;
+
+/**
+ * Launcher Activity that demonstrates the use of runtime permissions for Android M.
+ * It contains a summary sample description, sample log and a Fragment that calls callbacks on this
+ * Activity to illustrate parts of the runtime permissions API.
+ * <p>
+ * This Activity requests permissions to access the camera ({@link android.Manifest.permission#CAMERA})
+ * when the 'Show Camera' button is clicked to display the camera preview.
+ * Contacts permissions (({@link android.Manifest.permission#READ_CONTACTS} and ({@link
+ * android.Manifest.permission#WRITE_CONTACTS})) are requested when the 'Show and Add Contacts'
+ * button is
+ * clicked to display the first contact in the contacts database and to add a dummy contact
+ * directly
+ * to it. First, permissions are checked if they have already been granted through {@link
+ * android.app.Activity#checkSelfPermission(String)} (wrapped in {@link
+ * PermissionUtil#hasSelfPermission(Activity, String)} and {@link PermissionUtil#hasSelfPermission(Activity,
+ * String[])} for compatibility). If permissions have not been granted, they are requested through
+ * {@link Activity#requestPermissions(String[], int)} and the return value checked in {@link
+ * Activity#onRequestPermissionsResult(int, String[], int[])}.
+ * <p>
+ * If this sample is executed on a device running a platform version below M, all permissions
+ * declared
+ * in the Android manifest file are always granted at install time and cannot be requested at run
+ * time.
+ * <p>
+ * This sample targets the M platform and must therefore request permissions at runtime. Change the
+ * targetSdk in the file 'Application/build.gradle' to 22 to run the application in compatibility
+ * mode.
+ * Now, if a permission has been disable by the system through the application settings, disabled
+ * APIs provide compatibility data.
+ * For example the camera cannot be opened or an empty list of contacts is returned. No special
+ * action is required in this case.
+ * <p>
+ * (This class is based on the MainActivity used in the SimpleFragment sample template.)
+ */
+public class MainActivity extends SampleActivityBase {
+
+ public static final String TAG = "MainActivity";
+
+ /**
+ * Id to identify a camera permission request.
+ */
+ private static final int REQUEST_CAMERA = 0;
+
+ /**
+ * Id to identify a contacts permission request.
+ */
+ private static final int REQUEST_CONTACTS = 1;
+
+ /**
+ * Permissions required to read and write contacts. Used by the {@link ContactsFragment}.
+ */
+ private static String[] PERMISSIONS_CONTACT = {Manifest.permission.READ_CONTACTS,
+ Manifest.permission.WRITE_CONTACTS};
+
+ // Whether the Log Fragment is currently shown.
+ private boolean mLogShown;
+
+
+ /**
+ * Called when the 'show camera' button is clicked.
+ * Callback is defined in resource layout definition.
+ */
+ public void showCamera(View view) {
+ Log.i(TAG, "Show camera button pressed. Checking permission.");
+ // BEGIN_INCLUDE(camera_permission)
+ // Check if the Camera permission is already available.
+ if (PermissionUtil.hasSelfPermission(this, Manifest.permission.CAMERA)) {
+ Log.i(TAG,
+ "CAMERA permission has already been granted. Displaying camera preview.");
+ // Camera permissions is already available, show the camera preview.
+ showCameraPreview();
+ } else {
+ Log.i(TAG, "CAMERA permission has NOT been granted. Requesting permission.");
+ // Camera permission has not been granted. Request it.
+ requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA);
+ }
+ // END_INCLUDE(camera_permission)
+
+ }
+
+ /**
+ * Called when the 'show camera' button is clicked.
+ * Callback is defined in resource layout definition.
+ */
+ public void showContacts(View v) {
+ Log.i(TAG, "Show contacts button pressed. Checking permissions.");
+ // Verify that all required contact permissions have been granted.
+ if (PermissionUtil.hasSelfPermission(this, PERMISSIONS_CONTACT)) {
+ Log.i(TAG,
+ "Contact permissions have already been granted. Displaying contact details.");
+ // Contact permissions have been granted. Show the contacts fragment.
+ showContactDetails();
+ } else {
+ Log.i(TAG, "Contact permissions has NOT been granted. Requesting permission.");
+ // contact permissions has not been granted (read and write contacts). Request them.
+ requestPermissions(PERMISSIONS_CONTACT, REQUEST_CONTACTS);
+ }
+ }
+
+ /**
+ * Display the {@link CameraPreviewFragment} in the content area if the required Camera
+ * permission has been granted.
+ */
+ private void showCameraPreview() {
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance())
+ .addToBackStack("contacts")
+ .commit();
+ }
+
+ /**
+ * Display the {@link ContactsFragment} in the content area if the required contacts
+ * permissions
+ * have been granted.
+ */
+ private void showContactDetails() {
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.sample_content_fragment, ContactsFragment.newInstance())
+ .addToBackStack("contacts")
+ .commit();
+ }
+
+
+ /**
+ * Callback received when a permissions request has been completed.
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ int[] grantResults) {
+
+ if (requestCode == REQUEST_CAMERA) {
+ // BEGIN_INCLUDE(permission_result)
+ // Received permission result for camera permission.
+ Log.i(TAG, "Received response for Camera permission request.");
+
+ // Check if the only required permission has been granted
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // Camera permission has been granted, preview can be displayed
+ Log.i(TAG, "CAMERA permission has now been granted. Showing preview.");
+ Toast.makeText(this, R.string.permision_available_camera, Toast.LENGTH_SHORT)
+ .show();
+ } else {
+ Log.i(TAG, "CAMERA permission was NOT granted.");
+ Toast.makeText(this, R.string.permissions_not_granted, Toast.LENGTH_SHORT).show();
+
+ }
+ // END_INCLUDE(permission_result)
+
+ } else if (requestCode == REQUEST_CONTACTS) {
+ Log.i(TAG, "Received response for contact permissions request.");
+
+ // We have requested multiple permissions for contacts, so all of them need to be
+ // checked.
+ if (PermissionUtil.verifyPermissions(grantResults)) {
+ // All required permissions have been granted, display contacts fragment.
+ Toast.makeText(this, R.string.permision_available_contacts, Toast.LENGTH_SHORT)
+ .show();
+ } else {
+ Log.i(TAG, "Contacts permissions were NOT granted.");
+ Toast.makeText(this, R.string.permissions_not_granted, Toast.LENGTH_SHORT).show();
+ }
+
+ } else {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ }
+
+ /* Note: Methods and definitions below are only used to provide the UI for this sample and are
+ not relevant for the execution of the runtime permissions API. */
+
+
+ @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());
+ }
+
+ public void onBackClick(View view) {
+ getSupportFragmentManager().popBackStack();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ if (savedInstanceState == null) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ RuntimePermissionsFragment fragment = new RuntimePermissionsFragment();
+ transaction.replace(R.id.sample_content_fragment, fragment);
+ transaction.commit();
+ }
+
+ // This method sets up our custom logger, which will print all log messages to the device
+ // screen, as well as to adb logcat.
+ initializeLogging();
+ }
+}
diff --git a/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/PermissionUtil.java b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/PermissionUtil.java
new file mode 100644
index 0000000..ba2370f
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/PermissionUtil.java
@@ -0,0 +1,90 @@
+/*
+* Copyright 2015 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.system.runtimepermissions;
+
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.os.Build;
+
+/**
+ * Utility class that wraps access to the runtime permissions API in M and provides basic helper
+ * methods.
+ */
+public abstract class PermissionUtil {
+
+ /**
+ * Check that all given permissions have been granted by verifying that each entry in the
+ * given array is of the value {@link PackageManager#PERMISSION_GRANTED}.
+ *
+ * @see Activity#onRequestPermissionsResult(int, String[], int[])
+ */
+ public static boolean verifyPermissions(int[] grantResults) {
+ // Verify that each required permission has been granted, otherwise return false.
+ for (int result : grantResults) {
+ if (result != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if the Activity has access to all given permissions.
+ * Always returns true on platforms below M.
+ *
+ * @see Activity#checkSelfPermission(String)
+ */
+ public static boolean hasSelfPermission(Activity activity, String[] permissions) {
+ // Below Android M all permissions are granted at install time and are already available.
+ if (!isMNC()) {
+ return true;
+ }
+
+ // Verify that all required permissions have been granted
+ for (String permission : permissions) {
+ if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if the Activity has access to a given permission.
+ * Always returns true on platforms below M.
+ *
+ * @see Activity#checkSelfPermission(String)
+ */
+ public static boolean hasSelfPermission(Activity activity, String permission) {
+ // Below Android M all permissions are granted at install time and are already available.
+ if (!isMNC()) {
+ return true;
+ }
+
+ return activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ public static boolean isMNC() {
+ /*
+ TODO: In the Android M Preview release, checking if the platform is M is done through
+ the codename, not the version code. Once the API has been finalised, the following check
+ should be used: */
+ // return Build.VERSION.SDK_INT == Build.VERSION_CODES.MNC
+
+ return "MNC".equals(Build.VERSION.CODENAME);
+ }
+}
diff --git a/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/RuntimePermissionsFragment.java b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/RuntimePermissionsFragment.java
new file mode 100644
index 0000000..b35bfeb
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/RuntimePermissionsFragment.java
@@ -0,0 +1,50 @@
+/*
+* Copyright 2015 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.system.runtimepermissions;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class RuntimePermissionsFragment extends Fragment {
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View root = inflater.inflate(R.layout.fragment_main, null);
+
+ // BEGIN_INCLUDE(m_only_permission)
+ if (!PermissionUtil.isMNC()) {
+ /*
+ The contacts permissions have been declared in the AndroidManifest for Android M only.
+ They are not available on older platforms, so we are hiding the button to access the
+ contacts database.
+ This shows how new runtime-only permissions can be added, that do not apply to older
+ platform versions. This can be useful for automated updates where additional
+ permissions might prompt the user on upgrade.
+ */
+ root.findViewById(R.id.button_camera).setVisibility(View.GONE);
+ }
+ // END_INCLUDE(m_only_permission)
+
+ return root;
+ }
+}
diff --git a/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/camera/CameraPreview.java b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/camera/CameraPreview.java
new file mode 100644
index 0000000..1d25b51
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/camera/CameraPreview.java
@@ -0,0 +1,142 @@
+/*
+* Copyright 2015 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.system.runtimepermissions.camera;
+
+import android.content.Context;
+import android.hardware.Camera;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import java.io.IOException;
+
+/**
+ * Camera preview that displays a {@link Camera}.
+ *
+ * Handles basic lifecycle methods to display and stop the preview.
+ * <p>
+ * Implementation is based directly on the documentation at
+ * http://developer.android.com/guide/topics/media/camera.html
+ */
+public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
+
+ private static final String TAG = "CameraPreview";
+ private SurfaceHolder mHolder;
+ private Camera mCamera;
+ private Camera.CameraInfo mCameraInfo;
+ private int mDisplayOrientation;
+
+ public CameraPreview(Context context, Camera camera, Camera.CameraInfo cameraInfo,
+ int displayOrientation) {
+ super(context);
+
+ // Do not initialise if no camera has been set
+ if (camera == null || cameraInfo == null) {
+ return;
+ }
+ mCamera = camera;
+ mCameraInfo = cameraInfo;
+ mDisplayOrientation = displayOrientation;
+
+ // Install a SurfaceHolder.Callback so we get notified when the
+ // underlying surface is created and destroyed.
+ mHolder = getHolder();
+ mHolder.addCallback(this);
+ }
+
+ public void surfaceCreated(SurfaceHolder holder) {
+ // The Surface has been created, now tell the camera where to draw the preview.
+ try {
+ mCamera.setPreviewDisplay(holder);
+ mCamera.startPreview();
+ Log.d(TAG, "Camera preview started.");
+ } catch (IOException e) {
+ Log.d(TAG, "Error setting camera preview: " + e.getMessage());
+ }
+ }
+
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ // empty. Take care of releasing the Camera preview in your activity.
+ }
+
+ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+ // If your preview can change or rotate, take care of those events here.
+ // Make sure to stop the preview before resizing or reformatting it.
+
+ if (mHolder.getSurface() == null) {
+ // preview surface does not exist
+ Log.d(TAG, "Preview surface does not exist");
+ return;
+ }
+
+ // stop preview before making changes
+ try {
+ mCamera.stopPreview();
+ Log.d(TAG, "Preview stopped.");
+ } catch (Exception e) {
+ // ignore: tried to stop a non-existent preview
+ Log.d(TAG, "Error starting camera preview: " + e.getMessage());
+ }
+
+ int orientation = calculatePreviewOrientation(mCameraInfo, mDisplayOrientation);
+ mCamera.setDisplayOrientation(orientation);
+
+ try {
+ mCamera.setPreviewDisplay(mHolder);
+ mCamera.startPreview();
+ Log.d(TAG, "Camera preview started.");
+ } catch (Exception e) {
+ Log.d(TAG, "Error starting camera preview: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Calculate the correct orientation for a {@link Camera} preview that is displayed on screen.
+ *
+ * Implementation is based on the sample code provided in
+ * {@link Camera#setDisplayOrientation(int)}.
+ */
+ public static int calculatePreviewOrientation(Camera.CameraInfo info, int rotation) {
+ int degrees = 0;
+
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ degrees = 0;
+ break;
+ case Surface.ROTATION_90:
+ degrees = 90;
+ break;
+ case Surface.ROTATION_180:
+ degrees = 180;
+ break;
+ case Surface.ROTATION_270:
+ degrees = 270;
+ break;
+ }
+
+ int result;
+ if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ result = (info.orientation + degrees) % 360;
+ result = (360 - result) % 360; // compensate the mirror
+ } else { // back-facing
+ result = (info.orientation - degrees + 360) % 360;
+ }
+
+ return result;
+ }
+}
diff --git a/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/camera/CameraPreviewFragment.java b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/camera/CameraPreviewFragment.java
new file mode 100644
index 0000000..d0938f6
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/camera/CameraPreviewFragment.java
@@ -0,0 +1,112 @@
+/*
+* Copyright 2015 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.system.runtimepermissions.camera;
+
+import com.example.android.common.logger.Log;
+import com.example.android.system.runtimepermissions.R;
+
+import android.hardware.Camera;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+
+/**
+ * Displays a {@link CameraPreview} of the first {@link Camera}.
+ * An error message is displayed if the Camera is not available.
+ * <p>
+ * This Fragment is only used to illustrate that access to the Camera API has been granted (or
+ * denied) as part of the runtime permissions model. It is not relevant for the use of the
+ * permissions API.
+ * <p>
+ * Implementation is based directly on the documentation at
+ * http://developer.android.com/guide/topics/media/camera.html
+ */
+public class CameraPreviewFragment extends Fragment {
+
+ private static final String TAG = "CameraPreview";
+
+ /**
+ * Id of the camera to access. 0 is the first camera.
+ */
+ private static final int CAMERA_ID = 0;
+
+ private CameraPreview mPreview;
+ private Camera mCamera;
+
+ public static CameraPreviewFragment newInstance() {
+ return new CameraPreviewFragment();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ // Open an instance of the first camera and retrieve its info.
+ mCamera = getCameraInstance(CAMERA_ID);
+ Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+ Camera.getCameraInfo(CAMERA_ID, cameraInfo);
+
+ if (mCamera == null || cameraInfo == null) {
+ // Camera is not available, display error message
+ Toast.makeText(getActivity(), "Camera is not available.", Toast.LENGTH_SHORT).show();
+ return inflater.inflate(R.layout.fragment_camera_unavailable, null);
+ }
+
+ View root = inflater.inflate(R.layout.fragment_camera, null);
+
+ // Get the rotation of the screen to adjust the preview image accordingly.
+ final int displayRotation = getActivity().getWindowManager().getDefaultDisplay()
+ .getRotation();
+
+ // Create the Preview view and set it as the content of this Activity.
+ mPreview = new CameraPreview(getActivity(), mCamera, cameraInfo, displayRotation);
+ FrameLayout preview = (FrameLayout) root.findViewById(R.id.camera_preview);
+ preview.addView(mPreview);
+
+ return root;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ // Stop camera access
+ releaseCamera();
+ }
+
+ /** A safe way to get an instance of the Camera object. */
+ public static Camera getCameraInstance(int cameraId) {
+ Camera c = null;
+ try {
+ c = Camera.open(cameraId); // attempt to get a Camera instance
+ } catch (Exception e) {
+ // Camera is not available (in use or does not exist)
+ Log.d(TAG, "Camera " + cameraId + " is not available: " + e.getMessage());
+ }
+ return c; // returns null if camera is unavailable
+ }
+
+ private void releaseCamera() {
+ if (mCamera != null) {
+ mCamera.release(); // release the camera for other applications
+ mCamera = null;
+ }
+ }
+}
diff --git a/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/contacts/ContactsFragment.java b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/contacts/ContactsFragment.java
new file mode 100644
index 0000000..19f54fb
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/java/com/example/android/system/runtimepermissions/contacts/ContactsFragment.java
@@ -0,0 +1,189 @@
+/*
+* Copyright 2015 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.system.runtimepermissions.contacts;
+
+import com.example.android.common.logger.Log;
+import com.example.android.system.runtimepermissions.R;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+/**
+ * Displays the first contact stored on the device and contains an option to add a dummy contact.
+ * <p>
+ * This Fragment is only used to illustrate that access to the Contacts ContentProvider API has
+ * been granted (or denied) as part of the runtime permissions model. It is not relevant for the
+ * use
+ * of the permissions API.
+ * <p>
+ * This fragments demonstrates a basic use case for accessing the Contacts Provider. The
+ * implementation is based on the training guide available here:
+ * https://developer.android.com/training/contacts-provider/retrieve-names.html
+ */
+public class ContactsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
+
+ private static final String TAG = "Contacts";
+ private TextView mMessageText = null;
+
+ private static String DUMMY_CONTACT_NAME = "__DUMMY CONTACT from runtime permissions sample";
+
+ /**
+ * Projection for the content provider query includes the id and primary name of a contact.
+ */
+ private static final String[] PROJECTION = {ContactsContract.Contacts._ID,
+ ContactsContract.Contacts.DISPLAY_NAME_PRIMARY};
+ /**
+ * Sort order for the query. Sorted by primary name in ascending order.
+ */
+ private static final String ORDER = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + " ASC";
+
+
+ /**
+ * Creates a new instance of a ContactsFragment.
+ */
+ public static ContactsFragment newInstance() {
+ return new ContactsFragment();
+ }
+
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_contacts, container, false);
+
+ mMessageText = (TextView) rootView.findViewById(R.id.contact_message);
+
+ // Register a listener to add a dummy contact when a button is clicked.
+ Button button = (Button) rootView.findViewById(R.id.contact_add);
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ insertDummyContact();
+ }
+ });
+
+ // Register a listener to display the first contact when a button is clicked.
+ button = (Button) rootView.findViewById(R.id.contact_load);
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ loadContact();
+ }
+ });
+ return rootView;
+ }
+
+ /**
+ * Restart the Loader to query the Contacts content provider to display the first contact.
+ */
+ private void loadContact() {
+ getLoaderManager().restartLoader(0, null, this);
+ }
+
+ /**
+ * Initialises a new {@link CursorLoader} that queries the {@link ContactsContract}.
+ */
+ @Override
+ public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
+ return new CursorLoader(getActivity(), ContactsContract.Contacts.CONTENT_URI, PROJECTION,
+ null, null, ORDER);
+ }
+
+
+ /**
+ * Dislays either the name of the first contact or a message.
+ */
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+
+ if (cursor != null) {
+ final int totalCount = cursor.getCount();
+ if (totalCount > 0) {
+ cursor.moveToFirst();
+ String name = cursor
+ .getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
+ mMessageText.setText(
+ getResources().getString(R.string.contacts_string, totalCount, name));
+ Log.d(TAG, "First contact loaded: " + name);
+ Log.d(TAG, "Total number of contacts: " + totalCount);
+ Log.d(TAG, "Total number of contacts: " + totalCount);
+ } else {
+ Log.d(TAG, "List of contacts is empty.");
+ mMessageText.setText(R.string.contacts_empty);
+ }
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ mMessageText.setText(R.string.contacts_empty);
+ }
+
+ /**
+ * Accesses the Contacts content provider directly to insert a new contact.
+ * <p>
+ * The contact is called "__DUMMY ENTRY" and only contains a name.
+ */
+ private void insertDummyContact() {
+ // Two operations are needed to insert a new contact.
+ ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(2);
+
+ // First, set up a new raw contact.
+ ContentProviderOperation.Builder op =
+ ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+ .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
+ .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null);
+ operations.add(op.build());
+
+ // Next, set the name for the contact.
+ op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ .withValue(ContactsContract.Data.MIMETYPE,
+ ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+ .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
+ DUMMY_CONTACT_NAME);
+ operations.add(op.build());
+
+ // Apply the operations.
+ ContentResolver resolver = getActivity().getContentResolver();
+ try {
+ resolver.applyBatch(ContactsContract.AUTHORITY, operations);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Could not add a new contact: " + e.getMessage());
+ } catch (OperationApplicationException e) {
+ Log.d(TAG, "Could not add a new contact: " + e.getMessage());
+ }
+ }
+}
diff --git a/system/RuntimePermissions/Application/src/main/res/layout-w720dp/activity_main.xml b/system/RuntimePermissions/Application/src/main/res/layout-w720dp/activity_main.xml
new file mode 100644
index 0000000..c9a52f6
--- /dev/null
+++ b/system/RuntimePermissions/Application/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/system/RuntimePermissions/Application/src/main/res/layout/activity_main.xml b/system/RuntimePermissions/Application/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..64e8322
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/res/layout/activity_main.xml
@@ -0,0 +1,65 @@
+<!--
+ Copyright 2015 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/system/RuntimePermissions/Application/src/main/res/layout/fragment_camera.xml b/system/RuntimePermissions/Application/src/main/res/layout/fragment_camera.xml
new file mode 100644
index 0000000..ecdc54a
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/res/layout/fragment_camera.xml
@@ -0,0 +1,33 @@
+<!--
+ Copyright 2015 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">
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/back"
+ android:onClick="onBackClick"
+ android:layout_gravity="center_horizontal"/>
+
+ <FrameLayout
+ android:id="@+id/camera_preview"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ />
+</LinearLayout>
\ No newline at end of file
diff --git a/system/RuntimePermissions/Application/src/main/res/layout/fragment_camera_unavailable.xml b/system/RuntimePermissions/Application/src/main/res/layout/fragment_camera_unavailable.xml
new file mode 100644
index 0000000..200ebbc
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/res/layout/fragment_camera_unavailable.xml
@@ -0,0 +1,43 @@
+<!--
+ Copyright 2015 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">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/back"
+ android:onClick="onBackClick"
+ android:layout_gravity="center_horizontal"/>
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="fill_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ 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/camera_unavailable"/>
+
+ </ScrollView>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/system/RuntimePermissions/Application/src/main/res/layout/fragment_contacts.xml b/system/RuntimePermissions/Application/src/main/res/layout/fragment_contacts.xml
new file mode 100644
index 0000000..8a70fe2
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/res/layout/fragment_contacts.xml
@@ -0,0 +1,54 @@
+<!--
+ Copyright 2015 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:paddingLeft="@dimen/horizontal_page_margin"
+ android:paddingRight="@dimen/horizontal_page_margin"
+ android:paddingTop="@dimen/vertical_page_margin"
+ android:paddingBottom="@dimen/vertical_page_margin">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/back"
+ android:onClick="onBackClick"
+ android:layout_gravity="center_horizontal"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/contacts_intro"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/contact_message"/>
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/add_contact"
+ android:id="@+id/contact_add"/>
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/show_contact"
+ android:id="@+id/contact_load"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/system/RuntimePermissions/Application/src/main/res/layout/fragment_main.xml b/system/RuntimePermissions/Application/src/main/res/layout/fragment_main.xml
new file mode 100644
index 0000000..f9bfd5f
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/res/layout/fragment_main.xml
@@ -0,0 +1,48 @@
+<!--
+ Copyright 2015 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:paddingLeft="@dimen/horizontal_page_margin"
+ android:paddingRight="@dimen/horizontal_page_margin"
+ android:paddingTop="@dimen/vertical_page_margin"
+ android:paddingBottom="@dimen/vertical_page_margin"
+ android:orientation="vertical"
+ tools:context=".MainActivityFragment">
+
+ <TextView
+ android:text="@string/main_introduction"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/show_camera"
+ android:id="@+id/button_camera"
+ android:onClick="showCamera"/>
+
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/show_contacts"
+ android:id="@+id/button_contacts"
+ android:onClick="showContacts"/>
+
+</LinearLayout>
diff --git a/system/RuntimePermissions/Application/src/main/res/menu/main.xml b/system/RuntimePermissions/Application/src/main/res/menu/main.xml
new file mode 100644
index 0000000..b49c2c5
--- /dev/null
+++ b/system/RuntimePermissions/Application/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/system/RuntimePermissions/Application/src/main/res/mipmap-hdpi/ic_launcher.png b/system/RuntimePermissions/Application/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..3279054
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/system/RuntimePermissions/Application/src/main/res/mipmap-mdpi/ic_launcher.png b/system/RuntimePermissions/Application/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..225fe42
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/system/RuntimePermissions/Application/src/main/res/mipmap-xhdpi/ic_launcher.png b/system/RuntimePermissions/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..c129dbb
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/system/RuntimePermissions/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png b/system/RuntimePermissions/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b4ab118
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/system/RuntimePermissions/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/system/RuntimePermissions/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..99649fb
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/system/RuntimePermissions/Application/src/main/res/values/fragmentview_strings.xml b/system/RuntimePermissions/Application/src/main/res/values/fragmentview_strings.xml
new file mode 100644
index 0000000..7b9d9ec
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/res/values/fragmentview_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/system/RuntimePermissions/Application/src/main/res/values/strings.xml b/system/RuntimePermissions/Application/src/main/res/values/strings.xml
new file mode 100644
index 0000000..941f059
--- /dev/null
+++ b/system/RuntimePermissions/Application/src/main/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="contacts_string">Total number of contacts: %1$,d\nFirst contact:<b>%2$s</b></string>
+ <string name="contacts_none">No contacts stored on device.</string>
+ <string name="contacts_empty">Contacts not loaded.</string>
+ <string name="add_contact">Add a contact</string>
+ <string name="show_contact">Show first contact</string>
+ <string name="contacts_intro">This fragment demonstrates access to the contact database on the device.\n
+ Clicking \"Add a contact\" adds a new contact named "__DUMMY ENTRY" directly into the database.\n
+ Clicking \"Show first contact\" accesses the contact database to display the name of the first contact.</string>
+ <string name="camera_unavailable"><b>Camera could not be opened.</b>\nThis occurs when the camera is not available (for example it is already in use) or if the system has denied access (for example when camera access has been disabled).</string>
+ <string name="back">Back</string>
+ <string name="show_camera">Show camera preview</string>
+ <string name="show_contacts">Show and add contacts</string>
+ <string name="main_introduction">Click the buttons below to show a camera preview or access the contacts database.\nNote that the contacts option is only available on Android M to illustrate the use of optional, M-only permissions that are not requested on lower SDK platforms.</string>
+ <string name="permision_available_camera">Camera Permission has been granted. Preview can now be opened.</string>
+ <string name="permision_available_contacts">Contacts Permissions have been granted. Contacts screen can now be opened.</string>
+ <string name="permissions_not_granted">Permissions were not granted.</string>
+</resources>
\ No newline at end of file
diff --git a/system/RuntimePermissions/build.gradle b/system/RuntimePermissions/build.gradle
new file mode 100644
index 0000000..2b8d1ef
--- /dev/null
+++ b/system/RuntimePermissions/build.gradle
@@ -0,0 +1,11 @@
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+ pathToBuild "../../../../build"
+ pathToSamplesCommon "../../common"
+}
+apply from: "../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/system/RuntimePermissions/buildSrc/build.gradle b/system/RuntimePermissions/buildSrc/build.gradle
new file mode 100644
index 0000000..8c294c2
--- /dev/null
+++ b/system/RuntimePermissions/buildSrc/build.gradle
@@ -0,0 +1,15 @@
+repositories {
+ mavenCentral()
+}
+dependencies {
+ compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+ main {
+ groovy {
+ srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
+ }
+ }
+}
+
diff --git a/system/RuntimePermissions/gradle/wrapper/gradle-wrapper.jar b/system/RuntimePermissions/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/system/RuntimePermissions/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/system/RuntimePermissions/gradle/wrapper/gradle-wrapper.properties b/system/RuntimePermissions/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..8a27ebf
--- /dev/null
+++ b/system/RuntimePermissions/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun Dec 07 22:52:45 JST 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/system/RuntimePermissions/gradlew b/system/RuntimePermissions/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/system/RuntimePermissions/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/system/RuntimePermissions/gradlew.bat b/system/RuntimePermissions/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/system/RuntimePermissions/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/system/RuntimePermissions/screenshots/big_icon.png b/system/RuntimePermissions/screenshots/big_icon.png
new file mode 100644
index 0000000..706fc04
--- /dev/null
+++ b/system/RuntimePermissions/screenshots/big_icon.png
Binary files differ
diff --git a/system/RuntimePermissions/screenshots/screenshot-1.png b/system/RuntimePermissions/screenshots/screenshot-1.png
new file mode 100644
index 0000000..bb848d8
--- /dev/null
+++ b/system/RuntimePermissions/screenshots/screenshot-1.png
Binary files differ
diff --git a/system/RuntimePermissions/screenshots/screenshot-2.png b/system/RuntimePermissions/screenshots/screenshot-2.png
new file mode 100644
index 0000000..c98c212
--- /dev/null
+++ b/system/RuntimePermissions/screenshots/screenshot-2.png
Binary files differ
diff --git a/system/RuntimePermissions/settings.gradle b/system/RuntimePermissions/settings.gradle
new file mode 100644
index 0000000..9464a35
--- /dev/null
+++ b/system/RuntimePermissions/settings.gradle
@@ -0,0 +1 @@
+include 'Application'
diff --git a/system/RuntimePermissions/template-params.xml b/system/RuntimePermissions/template-params.xml
new file mode 100644
index 0000000..3baefc6
--- /dev/null
+++ b/system/RuntimePermissions/template-params.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2015 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.
+-->
+<sample>
+ <name>RuntimePermissions</name>
+ <group>System</group>
+ <package>com.example.android.system.runtimepermissions</package>
+
+ <minSdk>"MNC"</minSdk>
+ <targetSdkVersion>"MNC"</targetSdkVersion>
+ <compileSdkVersion>"android-MNC"</compileSdkVersion>
+
+
+ <dependency>com.android.support:appcompat-v7:21.+</dependency>
+
+ <strings>
+ <intro>
+ <![CDATA[
+ This sample shows runtime permissions available in the Android M and above.
+ Display the log to follow the execution.
+ If executed on an Android M device, an additional option to access contacts is shown.
+ ]]>
+ </intro>
+ </strings>
+
+ <template src="base"/>
+ <common src="logger"/>
+ <common src="activities"/>
+
+ <metadata>
+ <status>PUBLISHED</status>
+ <categories>System</categories>
+ <technologies>Android</technologies>
+ <languages>Java</languages>
+ <solutions>Mobile</solutions>
+ <level>BEGINNER</level>
+ <icon>screenshots/big_icon.png</icon>
+ <screenshots>
+ <img>screenshots/screenshot-1.png</img>
+ <img>screenshots/screenshot-2.png</img>
+ </screenshots>
+ <api_refs>
+ <android>android.app.Activity</android>
+ <android>android.Manifest.permission</android>
+ </api_refs>
+
+ <description>
+<![CDATA[
+This sample shows runtime permissions available in the Android M and above.
+It shows how to check and request permissions at runtime and how to declare permissions for M-devices
+only.
+]]>
+ </description>
+
+ <intro>
+<![CDATA[
+Android M introduced runtime permissions. Applications targeting M and above need to request their
+permissions at runtime.
+All permissions still need to be declared in the AndroidManifest. However, when accessing APIs that
+require a permission, the Activity or Fragment has to verify that the permission has been granted
+or request the missing permissions. Permissions are checked through
+Activity#requestPermissions(String[], int) and Fragment#requestPermissions(String[], int).
+Permission requests are sent through Activity#requestPermissions(String[]), and the response
+received in the callback Activity#onRequestPermissionsResult(int, permissions[], int[]).
+
+If an application targets an SDK below M, all permissions are granted at runtime and are available
+when the application is running. However, if permissions have been turned off in the system settings
+for an application targeting an SDK below M, the API will return empty or no data.
+]]>
+ </intro>
+ </metadata>
+</sample>
diff --git a/system/RuntimePermissionsBasic/Application/.gitignore b/system/RuntimePermissionsBasic/Application/.gitignore
new file mode 100644
index 0000000..6eb878d
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/.gitignore
@@ -0,0 +1,16 @@
+# 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.
+src/template/
+src/common/
+build.gradle
diff --git a/system/RuntimePermissionsBasic/Application/proguard-project.txt b/system/RuntimePermissionsBasic/Application/proguard-project.txt
new file mode 100644
index 0000000..0d8f171
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/proguard-project.txt
@@ -0,0 +1,20 @@
+ To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/system/RuntimePermissionsBasic/Application/src/main/AndroidManifest.xml b/system/RuntimePermissionsBasic/Application/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1f7ea6a
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/src/main/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2015 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.basicpermissions">
+
+ <!-- BEGIN_INCLUDE(manifest) -->
+
+ <!-- Note that all required permissions are declared here in the Android manifest.
+ On Android M and above, use of these permissions is only requested at run time. -->
+ <uses-permission android:name="android.permission.CAMERA"/>
+ <!-- END_INCLUDE(manifest) -->
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/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>
+
+ <!-- Activity that only displays the camera preview. -->
+ <activity
+ android:name=".camera.CameraPreviewActivity"
+ android:exported="false"/>
+
+ </application>
+
+</manifest>
diff --git a/system/RuntimePermissionsBasic/Application/src/main/java/com/example/android/basicpermissions/MainActivity.java b/system/RuntimePermissionsBasic/Application/src/main/java/com/example/android/basicpermissions/MainActivity.java
new file mode 100644
index 0000000..9de3b01
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/src/main/java/com/example/android/basicpermissions/MainActivity.java
@@ -0,0 +1,135 @@
+/*
+* Copyright 2015 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.basicpermissions;
+
+import com.example.android.basicpermissions.camera.CameraPreviewActivity;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Toast;
+
+/**
+ * Launcher Activity that demonstrates the use of runtime permissions for Android M.
+ * This Activity requests permissions to access the camera
+ * ({@link android.Manifest.permission#CAMERA})
+ * when the 'Show Camera Preview' button is clicked to start {@link CameraPreviewActivity} once
+ * the permission has been granted.
+ * <p>
+ * First, the status of the Camera permission is checked using {@link
+ * Activity#checkSelfPermission(String)}.
+ * If it has not been granted ({@link PackageManager#PERMISSION_GRANTED}), it is requested by
+ * calling
+ * {@link Activity#requestPermissions(String[], int)}. The result of the request is returned in
+ * {@link Activity#onRequestPermissionsResult(int, String[], int[])}, which starts {@link
+ * CameraPreviewActivity}
+ * if the permission has been granted.
+ */
+public class MainActivity extends Activity {
+
+ private static final int PERMISSION_REQUEST_CAMERA = 0;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ // Register a listener for the 'Show Camera Preview' button.
+ Button b = (Button) findViewById(R.id.button_open_camera);
+ b.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ showCameraPreview();
+ }
+ });
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ int[] grantResults) {
+ // BEGIN_INCLUDE(onRequestPermissionsResult)
+ if (requestCode == PERMISSION_REQUEST_CAMERA) {
+ // Request for camera permission.
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // Permission has been granted. Start camera preview Activity.
+ Toast.makeText(this, "Camera permission was granted. Starting preview.",
+ Toast.LENGTH_SHORT)
+ .show();
+ startCamera();
+ } else {
+ // Permission request was denied.
+ Toast.makeText(this, "Camera permission request was denied.", Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
+ // END_INCLUDE(onRequestPermissionsResult)
+ }
+
+ private void showCameraPreview() {
+ // BEGIN_INCLUDE(startCamera)
+ if (isMNC()) {
+ // On Android M and above, need to check if permission has been granted at runtime.
+ if (checkSelfPermission(Manifest.permission.CAMERA)
+ == PackageManager.PERMISSION_GRANTED) {
+ // Permission is available, start camera preview
+ startCamera();
+ Toast.makeText(this,
+ "Camera permission has already been granted. Starting preview.",
+ Toast.LENGTH_SHORT).show();
+ } else {
+ // Permission has not been granted and must be requested.
+ Toast.makeText(this,
+ "Permission is not available. Requesting camera permission.",
+ Toast.LENGTH_SHORT).show();
+ requestPermissions(new String[]{Manifest.permission.CAMERA},
+ PERMISSION_REQUEST_CAMERA);
+ }
+ } else {
+ /*
+ Below Android M all permissions have already been grated at install time and do not
+ need to verified or requested.
+ If a permission has been disabled in the system settings, the API will return
+ unavailable or empty data instead. */
+ Toast.makeText(this,
+ "Requested permissions are granted at install time below M and are always "
+ + "available at runtime.",
+ Toast.LENGTH_SHORT).show();
+ startCamera();
+ }
+ // END_INCLUDE(startCamera)
+ }
+
+ private void startCamera() {
+ Intent intent = new Intent(this, CameraPreviewActivity.class);
+ startActivity(intent);
+ }
+
+ public static boolean isMNC() {
+ /*
+ TODO: In the Android M Preview release, checking if the platform is M is done through
+ the codename, not the version code. Once the API has been finalised, the following check
+ should be used: */
+ // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.MNC
+
+ return "MNC".equals(Build.VERSION.CODENAME);
+ }
+}
diff --git a/system/RuntimePermissionsBasic/Application/src/main/java/com/example/android/basicpermissions/camera/CameraPreview.java b/system/RuntimePermissionsBasic/Application/src/main/java/com/example/android/basicpermissions/camera/CameraPreview.java
new file mode 100644
index 0000000..7abf2d8
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/src/main/java/com/example/android/basicpermissions/camera/CameraPreview.java
@@ -0,0 +1,142 @@
+/*
+* Copyright 2015 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.basicpermissions.camera;
+
+import android.content.Context;
+import android.hardware.Camera;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import java.io.IOException;
+
+/**
+ * Camera preview that displays a {@link Camera}.
+ *
+ * Handles basic lifecycle methods to display and stop the preview.
+ * <p>
+ * Implementation is based directly on the documentation at
+ * http://developer.android.com/guide/topics/media/camera.html
+ */
+public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
+
+ private static final String TAG = "CameraPreview";
+ private SurfaceHolder mHolder;
+ private Camera mCamera;
+ private Camera.CameraInfo mCameraInfo;
+ private int mDisplayOrientation;
+
+ public CameraPreview(Context context, Camera camera, Camera.CameraInfo cameraInfo,
+ int displayOrientation) {
+ super(context);
+
+ // Do not initialise if no camera has been set
+ if (camera == null || cameraInfo == null) {
+ return;
+ }
+ mCamera = camera;
+ mCameraInfo = cameraInfo;
+ mDisplayOrientation = displayOrientation;
+
+ // Install a SurfaceHolder.Callback so we get notified when the
+ // underlying surface is created and destroyed.
+ mHolder = getHolder();
+ mHolder.addCallback(this);
+ }
+
+ public void surfaceCreated(SurfaceHolder holder) {
+ // The Surface has been created, now tell the camera where to draw the preview.
+ try {
+ mCamera.setPreviewDisplay(holder);
+ mCamera.startPreview();
+ Log.d(TAG, "Camera preview started.");
+ } catch (IOException e) {
+ Log.d(TAG, "Error setting camera preview: " + e.getMessage());
+ }
+ }
+
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ // empty. Take care of releasing the Camera preview in your activity.
+ }
+
+ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+ // If your preview can change or rotate, take care of those events here.
+ // Make sure to stop the preview before resizing or reformatting it.
+
+ if (mHolder.getSurface() == null) {
+ // preview surface does not exist
+ Log.d(TAG, "Preview surface does not exist");
+ return;
+ }
+
+ // stop preview before making changes
+ try {
+ mCamera.stopPreview();
+ Log.d(TAG, "Preview stopped.");
+ } catch (Exception e) {
+ // ignore: tried to stop a non-existent preview
+ Log.d(TAG, "Error starting camera preview: " + e.getMessage());
+ }
+
+ int orientation = calculatePreviewOrientation(mCameraInfo, mDisplayOrientation);
+ mCamera.setDisplayOrientation(orientation);
+
+ try {
+ mCamera.setPreviewDisplay(mHolder);
+ mCamera.startPreview();
+ Log.d(TAG, "Camera preview started.");
+ } catch (Exception e) {
+ Log.d(TAG, "Error starting camera preview: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Calculate the correct orientation for a {@link Camera} preview that is displayed on screen.
+ *
+ * Implementation is based on the sample code provided in
+ * {@link Camera#setDisplayOrientation(int)}.
+ */
+ public static int calculatePreviewOrientation(Camera.CameraInfo info, int rotation) {
+ int degrees = 0;
+
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ degrees = 0;
+ break;
+ case Surface.ROTATION_90:
+ degrees = 90;
+ break;
+ case Surface.ROTATION_180:
+ degrees = 180;
+ break;
+ case Surface.ROTATION_270:
+ degrees = 270;
+ break;
+ }
+
+ int result;
+ if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ result = (info.orientation + degrees) % 360;
+ result = (360 - result) % 360; // compensate the mirror
+ } else { // back-facing
+ result = (info.orientation - degrees + 360) % 360;
+ }
+
+ return result;
+ }
+}
diff --git a/system/RuntimePermissionsBasic/Application/src/main/java/com/example/android/basicpermissions/camera/CameraPreviewActivity.java b/system/RuntimePermissionsBasic/Application/src/main/java/com/example/android/basicpermissions/camera/CameraPreviewActivity.java
new file mode 100644
index 0000000..ee589d9
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/src/main/java/com/example/android/basicpermissions/camera/CameraPreviewActivity.java
@@ -0,0 +1,104 @@
+/*
+* Copyright 2015 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.basicpermissions.camera;
+
+import com.example.android.basicpermissions.R;
+
+import android.app.Activity;
+import android.hardware.Camera;
+import android.os.Bundle;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+
+/**
+ * Displays a {@link CameraPreview} of the first {@link Camera}.
+ * An error message is displayed if the Camera is not available.
+ * <p>
+ * This Activity is only used to illustrate that access to the Camera API has been granted (or
+ * denied) as part of the runtime permissions model. It is not relevant for the use of the
+ * permissions API.
+ * <p>
+ * Implementation is based directly on the documentation at
+ * http://developer.android.com/guide/topics/media/camera.html
+ */
+public class CameraPreviewActivity extends Activity {
+
+ private static final String TAG = "CameraPreview";
+
+ /**
+ * Id of the camera to access. 0 is the first camera.
+ */
+ private static final int CAMERA_ID = 0;
+
+ private CameraPreview mPreview;
+ private Camera mCamera;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Open an instance of the first camera and retrieve its info.
+ mCamera = getCameraInstance(CAMERA_ID);
+ Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+ Camera.getCameraInfo(CAMERA_ID, cameraInfo);
+
+ if (mCamera == null || cameraInfo == null) {
+ // Camera is not available, display error message
+ Toast.makeText(this, "Camera is not available.", Toast.LENGTH_SHORT).show();
+ setContentView(R.layout.activity_camera_unavailable);
+ } else {
+
+ setContentView(R.layout.activity_camera);
+
+ // Get the rotation of the screen to adjust the preview image accordingly.
+ final int displayRotation = getWindowManager().getDefaultDisplay()
+ .getRotation();
+
+ // Create the Preview view and set it as the content of this Activity.
+ mPreview = new CameraPreview(this, mCamera, cameraInfo, displayRotation);
+ FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
+ preview.addView(mPreview);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ // Stop camera access
+ releaseCamera();
+ }
+
+ /** A safe way to get an instance of the Camera object. */
+ private Camera getCameraInstance(int cameraId) {
+ Camera c = null;
+ try {
+ c = Camera.open(cameraId); // attempt to get a Camera instance
+ } catch (Exception e) {
+ // Camera is not available (in use or does not exist)
+ Toast.makeText(this, "Camera " + cameraId + " is not available: " + e.getMessage(),
+ Toast.LENGTH_SHORT).show();
+ }
+ return c; // returns null if camera is unavailable
+ }
+
+ private void releaseCamera() {
+ if (mCamera != null) {
+ mCamera.release(); // release the camera for other applications
+ mCamera = null;
+ }
+ }
+}
diff --git a/system/RuntimePermissionsBasic/Application/src/main/res/layout/activity_camera.xml b/system/RuntimePermissionsBasic/Application/src/main/res/layout/activity_camera.xml
new file mode 100644
index 0000000..b12eee1
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/src/main/res/layout/activity_camera.xml
@@ -0,0 +1,22 @@
+<!--
+ Copyright 2015 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:id="@+id/camera_preview"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ />
diff --git a/system/RuntimePermissionsBasic/Application/src/main/res/layout/activity_camera_unavailable.xml b/system/RuntimePermissionsBasic/Application/src/main/res/layout/activity_camera_unavailable.xml
new file mode 100644
index 0000000..af0efe5
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/src/main/res/layout/activity_camera_unavailable.xml
@@ -0,0 +1,37 @@
+<!--
+ Copyright 2015 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:paddingLeft="@dimen/horizontal_page_margin"
+ android:paddingRight="@dimen/horizontal_page_margin"
+ android:paddingTop="@dimen/vertical_page_margin"
+ android:paddingBottom="@dimen/vertical_page_margin">
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="fill_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/camera_unavailable"/>
+
+ </ScrollView>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/system/RuntimePermissionsBasic/Application/src/main/res/layout/activity_main.xml b/system/RuntimePermissionsBasic/Application/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..c3bf99c
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/src/main/res/layout/activity_main.xml
@@ -0,0 +1,40 @@
+<!--
+ Copyright 2015 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:paddingLeft="@dimen/horizontal_page_margin"
+ android:paddingRight="@dimen/horizontal_page_margin"
+ android:paddingTop="@dimen/vertical_page_margin"
+ android:paddingBottom="@dimen/vertical_page_margin"
+ android:orientation="vertical"
+ tools:context=".MainActivity">
+
+ <TextView
+ android:text="@string/intro"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/horizontal_page_margin"/>
+
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Open Camera Preview"
+ android:id="@+id/button_open_camera"/>
+</LinearLayout>
diff --git a/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-hdpi/ic_launcher.png b/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a7cee76
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-mdpi/ic_launcher.png b/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..378d029
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-xhdpi/ic_launcher.png b/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..c898742
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png b/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..ad4f1df
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..484f298
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/system/RuntimePermissionsBasic/Application/src/main/res/values/strings.xml b/system/RuntimePermissionsBasic/Application/src/main/res/values/strings.xml
new file mode 100644
index 0000000..eb0b7c4
--- /dev/null
+++ b/system/RuntimePermissionsBasic/Application/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+<resources>
+ <string name="show_camera">Show camera preview</string>
+ <string name="camera_unavailable"><b>Camera could not be opened.</b>\nThis occurs when the camera is not available (for example it is already in use) or if the system has denied access (for example when camera access has been disabled).</string>
+ <string name="intro">This sample shows a basic implementation for requesting permissions at runtime.\nClick the button below to request the Camera permission and open a full-screen camera preview.</string>
+
+</resources>
diff --git a/system/RuntimePermissionsBasic/build.gradle b/system/RuntimePermissionsBasic/build.gradle
new file mode 100644
index 0000000..2b8d1ef
--- /dev/null
+++ b/system/RuntimePermissionsBasic/build.gradle
@@ -0,0 +1,11 @@
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+ pathToBuild "../../../../build"
+ pathToSamplesCommon "../../common"
+}
+apply from: "../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/system/RuntimePermissionsBasic/buildSrc/build.gradle b/system/RuntimePermissionsBasic/buildSrc/build.gradle
new file mode 100644
index 0000000..8c294c2
--- /dev/null
+++ b/system/RuntimePermissionsBasic/buildSrc/build.gradle
@@ -0,0 +1,15 @@
+repositories {
+ mavenCentral()
+}
+dependencies {
+ compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+ main {
+ groovy {
+ srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
+ }
+ }
+}
+
diff --git a/system/RuntimePermissionsBasic/gradle/wrapper/gradle-wrapper.jar b/system/RuntimePermissionsBasic/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/system/RuntimePermissionsBasic/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/system/RuntimePermissionsBasic/gradle/wrapper/gradle-wrapper.properties b/system/RuntimePermissionsBasic/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..8a27ebf
--- /dev/null
+++ b/system/RuntimePermissionsBasic/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun Dec 07 22:52:45 JST 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/system/RuntimePermissionsBasic/gradlew b/system/RuntimePermissionsBasic/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/system/RuntimePermissionsBasic/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/system/RuntimePermissionsBasic/gradlew.bat b/system/RuntimePermissionsBasic/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/system/RuntimePermissionsBasic/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/system/RuntimePermissionsBasic/screenshots/big_icon.png b/system/RuntimePermissionsBasic/screenshots/big_icon.png
new file mode 100644
index 0000000..14c53b6
--- /dev/null
+++ b/system/RuntimePermissionsBasic/screenshots/big_icon.png
Binary files differ
diff --git a/system/RuntimePermissionsBasic/screenshots/screenshot-1.png b/system/RuntimePermissionsBasic/screenshots/screenshot-1.png
new file mode 100644
index 0000000..6c31c38
--- /dev/null
+++ b/system/RuntimePermissionsBasic/screenshots/screenshot-1.png
Binary files differ
diff --git a/system/RuntimePermissionsBasic/settings.gradle b/system/RuntimePermissionsBasic/settings.gradle
new file mode 100644
index 0000000..9464a35
--- /dev/null
+++ b/system/RuntimePermissionsBasic/settings.gradle
@@ -0,0 +1 @@
+include 'Application'
diff --git a/system/RuntimePermissionsBasic/template-params.xml b/system/RuntimePermissionsBasic/template-params.xml
new file mode 100644
index 0000000..92021d4
--- /dev/null
+++ b/system/RuntimePermissionsBasic/template-params.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2015 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.
+-->
+<sample>
+ <name>RuntimePermissionsBasic</name>
+ <group>System</group>
+ <package>com.example.android.basicpermissions</package>
+
+ <minSdk>"MNC"</minSdk>
+ <targetSdkVersion>"MNC"</targetSdkVersion>
+ <compileSdkVersion>"android-MNC"</compileSdkVersion>
+
+ <dependency>com.android.support:appcompat-v7:21.+</dependency>
+
+ <strings>
+ <intro>
+ <![CDATA[
+ This sample shows runtime permissions available in the Android M and above.
+ This sample shows a basic implementation for requesting permissions at runtime. Click the button to request the Camera permission and open a full-screen camera preview.
+Note: The "RuntimePermissions" sample provides a more complete overview over the runtime permission features available.
+ ]]>
+ </intro>
+ </strings>
+
+ <template src="base"/>
+
+ <metadata>
+ <status>PUBLISHED</status>
+ <categories>System</categories>
+ <technologies>Android</technologies>
+ <languages>Java</languages>
+ <solutions>Mobile</solutions>
+ <level>BEGINNER</level>
+ <icon>screenshots/big_icon.png</icon>
+ <screenshots>
+ <img>screenshots/screenshot-1.png</img>
+ </screenshots>
+ <api_refs>
+ <android>android.app.Activity</android>
+ <android>android.Manifest.permission</android>
+ </api_refs>
+
+ <description>
+<![CDATA[
+This basic sample shows runtime permissions available in the Android M and above.
+It shows how to use the new runtime permission API to check for and request permissions.
+]]>
+ </description>
+
+ <intro>
+<![CDATA[
+Android M introduced runtime permissions. Applications targeting M and above need to request their
+permissions at runtime.
+This sample introduces the basic use of the runtime permissions API by checking for permissions (Activity#checkSelfPermission(String)), requesting permissions (Activity#requestPermissions(String[],int))
+and handling the permission request callback (Activity#onRequestPermissionsResult(int, permissions[], int[])).
+
+See the "RuntimePermissions" sample for a more complete description and reference implementation.
+]]>
+ </intro>
+ </metadata>
+</sample>
diff --git a/ui/actionbarcompat/ActionBarCompat-ListViewModalSelect/template-params.xml b/ui/actionbarcompat/ActionBarCompat-ListViewModalSelect/template-params.xml
index 03f6877..3c7e83a 100644
--- a/ui/actionbarcompat/ActionBarCompat-ListViewModalSelect/template-params.xml
+++ b/ui/actionbarcompat/ActionBarCompat-ListViewModalSelect/template-params.xml
@@ -25,7 +25,7 @@
<dependency>com.android.support:appcompat-v7:21.0.2</dependency>
<!-- change minSdk if needed-->
- <minSdk>7</minSdk>
+ <minSdk>8</minSdk>
<strings>
<intro>
diff --git a/ui/views/NavigationDrawer/template-params.xml b/ui/views/NavigationDrawer/template-params.xml
index af92a68..559f32e 100644
--- a/ui/views/NavigationDrawer/template-params.xml
+++ b/ui/views/NavigationDrawer/template-params.xml
@@ -25,7 +25,7 @@
<auto_add_support_lib>false</auto_add_support_lib>
<!-- change minSdk if needed-->
- <minSdk>14</minSdk>
+ <minSdk>21</minSdk>
<compileSdkVersion>21</compileSdkVersion>
diff --git a/ui/views/SlidingTabs/SlidingTabsBasic/template-params.xml b/ui/views/SlidingTabs/SlidingTabsBasic/template-params.xml
index 7d6c97f..fe39dab 100644
--- a/ui/views/SlidingTabs/SlidingTabsBasic/template-params.xml
+++ b/ui/views/SlidingTabs/SlidingTabsBasic/template-params.xml
@@ -23,7 +23,7 @@
<package>com.example.android.slidingtabsbasic</package>
<!-- change minSdk if needed-->
- <minSdk>4</minSdk>
+ <minSdk>14</minSdk>
<strings>
diff --git a/ui/views/SlidingTabs/SlidingTabsColors/template-params.xml b/ui/views/SlidingTabs/SlidingTabsColors/template-params.xml
index ecdad8c..752927b 100644
--- a/ui/views/SlidingTabs/SlidingTabsColors/template-params.xml
+++ b/ui/views/SlidingTabs/SlidingTabsColors/template-params.xml
@@ -24,7 +24,7 @@
<!-- change minSdk if needed-->
- <minSdk>4</minSdk>
+ <minSdk>14</minSdk>
<strings>
diff --git a/ui/views/SwipeRefreshLayout/SwipeRefreshLayoutBasic/template-params.xml b/ui/views/SwipeRefreshLayout/SwipeRefreshLayoutBasic/template-params.xml
index 0b99bcc..ddce9d1 100644
--- a/ui/views/SwipeRefreshLayout/SwipeRefreshLayoutBasic/template-params.xml
+++ b/ui/views/SwipeRefreshLayout/SwipeRefreshLayoutBasic/template-params.xml
@@ -23,7 +23,7 @@
<package>com.example.android.swiperefreshlayoutbasic</package>
<!-- change minSdk if needed-->
- <minSdk>10</minSdk>
+ <minSdk>14</minSdk>
<strings>
<intro>
diff --git a/ui/views/SwipeRefreshLayout/SwipeRefreshListFragment/template-params.xml b/ui/views/SwipeRefreshLayout/SwipeRefreshListFragment/template-params.xml
index 4210182..13230a5 100644
--- a/ui/views/SwipeRefreshLayout/SwipeRefreshListFragment/template-params.xml
+++ b/ui/views/SwipeRefreshLayout/SwipeRefreshListFragment/template-params.xml
@@ -21,7 +21,7 @@
<name>SwipeRefreshListFragment</name>
<group>UI</group>
<package>com.example.android.swiperefreshlistfragment</package>
- <minSdk>10</minSdk>
+ <minSdk>14</minSdk>
<strings>
<intro>
<![CDATA[
diff --git a/ui/views/SwipeRefreshLayout/SwipeRefreshMultipleViews/template-params.xml b/ui/views/SwipeRefreshLayout/SwipeRefreshMultipleViews/template-params.xml
index bb0054b..ad1ad5e 100644
--- a/ui/views/SwipeRefreshLayout/SwipeRefreshMultipleViews/template-params.xml
+++ b/ui/views/SwipeRefreshLayout/SwipeRefreshMultipleViews/template-params.xml
@@ -23,7 +23,7 @@
<package>com.example.android.swiperefreshmultipleviews</package>
<!-- change minSdk if needed-->
- <minSdk>10</minSdk>
+ <minSdk>14</minSdk>
<strings>
<intro>
<![CDATA[
diff --git a/ui/window/ImmersiveMode/template-params.xml b/ui/window/ImmersiveMode/template-params.xml
index 8959099..da1e511 100644
--- a/ui/window/ImmersiveMode/template-params.xml
+++ b/ui/window/ImmersiveMode/template-params.xml
@@ -20,7 +20,7 @@
<package>com.example.android.immersivemode</package>
<!--TODO: change minSdk if needed-->
- <minSdk>4</minSdk>
+ <minSdk>11</minSdk>
<strings>
diff --git a/wearable/wear/AlwaysOn/Wearable/src/main/AndroidManifest.xml b/wearable/wear/AlwaysOn/Wearable/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..12c4b03
--- /dev/null
+++ b/wearable/wear/AlwaysOn/Wearable/src/main/AndroidManifest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.wearable.wear.alwayson">
+
+ <uses-sdk android:minSdkVersion="20" android:targetSdkVersion="22" />
+
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
+
+ <uses-feature android:name="android.hardware.type.watch" />
+
+ <application
+ android:allowBackup="false"
+ android:label="@string/app_name">
+
+ <!--If you want your app to run on pre-22, then set required to false -->
+ <uses-library android:name="com.google.android.wearable" android:required="false" />
+
+ <!--
+ To update the screen more than once per minute in ambient mode, developers should set
+ the launch mode for the activity to single instance. Otherwise, the AlarmManager launch
+ intent will open a new activity every time the Alarm is triggered rather than reusing
+ the same (already active) Activity.
+ -->
+ <activity android:name="com.example.android.wearable.wear.alwayson.MainActivity"
+ android:label="@string/app_name"
+ android:launchMode="singleInstance"
+ android:configChanges="orientation|keyboardHidden"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+</manifest>
diff --git a/wearable/wear/AlwaysOn/Wearable/src/main/java/com/example/android/wearable/wear/alwayson/MainActivity.java b/wearable/wear/AlwaysOn/Wearable/src/main/java/com/example/android/wearable/wear/alwayson/MainActivity.java
new file mode 100644
index 0000000..3673545
--- /dev/null
+++ b/wearable/wear/AlwaysOn/Wearable/src/main/java/com/example/android/wearable/wear/alwayson/MainActivity.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2015 Google Inc. All Rights Reserved.
+ *
+ * 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.wearable.wear.alwayson;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.wearable.activity.WearableActivity;
+import android.support.wearable.view.WatchViewStub;
+import android.util.Log;
+import android.widget.TextView;
+
+import java.lang.ref.WeakReference;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Demonstrates support for Ambient screens by extending WearableActivity and overriding
+ * onEnterAmbient, onUpdateAmbient, and onExitAmbient.
+ *
+ * There are two modes (Active and Ambient). To trigger future updates (data/screen), we use a
+ * custom Handler for the "Active" mode and an Alarm for the "Ambient" mode.
+ *
+ * Why don't we use just one? Handlers are generally less battery intensive and can be triggered
+ * every second. However, they can not wake up the processor (common in Ambient mode).
+ *
+ * Alarms can wake up the processor (what we need for Ambient), but they struggle with quick updates
+ * (less than one second) and are much less efficient compared to Handlers.
+ *
+ * Therefore, we use Handlers for "Active" mode (can trigger every second and are better on the
+ * battery), and we use Alarms for "Ambient" mode (only need to update once every 20 seconds and
+ * they can wake up a sleeping processor).
+ *
+ * Again, the Activity waits 20 seconds between doing any processing (getting data, updating screen
+ * etc.) while in ambient mode to conserving battery life (processor allowed to sleep). If you can
+ * hold off on updates for a full minute, you can throw away all the Alarm code and just use
+ * onUpdateAmbient() to save even more battery life.
+ *
+ * As always, you will still want to apply the performance guidelines outlined in the Watch Faces
+ * documention to your app.
+ *
+ * Finally, in ambient mode, this Activity follows the same best practices outlined in the
+ * Watch Faces API documentation, e.g., keep most pixels black, avoid large blocks of white pixels,
+ * use only black and white, and disable anti-aliasing.
+ *
+ */
+public class MainActivity extends WearableActivity {
+
+ private static final String TAG = "MainActivity";
+
+ /** Custom 'what' for Message sent to Handler. */
+ private static final int MSG_UPDATE_SCREEN = 0;
+
+ /** Milliseconds between updates based on state. */
+ private static final long ACTIVE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(1);
+ private static final long AMBIENT_INTERVAL_MS = TimeUnit.SECONDS.toMillis(20);
+
+ /** Tracks latest ambient details, such as burnin offsets, etc. */
+ private Bundle mAmbientDetails;
+
+ private TextView mTimeTextView;
+ private TextView mTimeStampTextView;
+ private TextView mStateTextView;
+ private TextView mUpdateRateTextView;
+ private TextView mDrawCountTextView;
+
+ private final SimpleDateFormat sDateFormat =
+ new SimpleDateFormat("HH:mm:ss", Locale.US);
+
+ private volatile int mDrawCount = 0;
+
+
+ /**
+ * Since the handler (used in active mode) can't wake up the processor when the device is in
+ * ambient mode and undocked, we use an Alarm to cover ambient mode updates when we need them
+ * more frequently than every minute. Remember, if getting updates once a minute in ambient
+ * mode is enough, you can do away with the Alarm code and just rely on the onUpdateAmbient()
+ * callback.
+ */
+ private AlarmManager mAmbientStateAlarmManager;
+ private PendingIntent mAmbientStatePendingIntent;
+
+ /**
+ * This custom handler is used for updates in "Active" mode. We use a separate static class to
+ * help us avoid memory leaks.
+ */
+ private final Handler mActiveModeUpdateHandler = new UpdateHandler(this);
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.d(TAG, "onCreate()");
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_main);
+
+ setAmbientEnabled();
+
+ mAmbientStateAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ Intent ambientStateIntent = new Intent(getApplicationContext(), MainActivity.class);
+
+ mAmbientStatePendingIntent = PendingIntent.getActivity(
+ getApplicationContext(),
+ 0 /* requestCode */,
+ ambientStateIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+
+ /** Determines whether watch is round or square and applies proper view. **/
+ final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
+ stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
+ @Override
+ public void onLayoutInflated(WatchViewStub stub) {
+
+ mTimeTextView = (TextView) stub.findViewById(R.id.time);
+ mTimeStampTextView = (TextView) stub.findViewById(R.id.time_stamp);
+ mStateTextView = (TextView) stub.findViewById(R.id.state);
+ mUpdateRateTextView = (TextView) stub.findViewById(R.id.update_rate);
+ mDrawCountTextView = (TextView) stub.findViewById(R.id.draw_count);
+
+ refreshDisplayAndSetNextUpdate();
+ }
+ });
+ }
+
+ /**
+ * This is mostly triggered by the Alarms we set in Ambient mode and informs us we need to
+ * update the screen (and process any data).
+ */
+ @Override
+ public void onNewIntent(Intent intent) {
+ Log.d(TAG, "onNewIntent(): " + intent);
+ super.onNewIntent(intent);
+
+ setIntent(intent);
+
+ refreshDisplayAndSetNextUpdate();
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy()");
+
+ mActiveModeUpdateHandler.removeMessages(MSG_UPDATE_SCREEN);
+ mAmbientStateAlarmManager.cancel(mAmbientStatePendingIntent);
+
+ super.onDestroy();
+ }
+
+ /**
+ * Prepares UI for Ambient view.
+ */
+ @Override
+ public void onEnterAmbient(Bundle ambientDetails) {
+ Log.d(TAG, "onEnterAmbient()");
+ super.onEnterAmbient(ambientDetails);
+
+ /**
+ * In this sample, we aren't using the ambient details bundle (EXTRA_BURN_IN_PROTECTION or
+ * EXTRA_LOWBIT_AMBIENT), but if you need them, you can pull them from the local variable
+ * set here.
+ */
+ mAmbientDetails = ambientDetails;
+
+ /** Clears Handler queue (only needed for updates in active mode). */
+ mActiveModeUpdateHandler.removeMessages(MSG_UPDATE_SCREEN);
+
+ /**
+ * Following best practices outlined in WatchFaces API (keeping most pixels black,
+ * avoiding large blocks of white pixels, using only black and white,
+ * and disabling anti-aliasing anti-aliasing, etc.)
+ */
+ mStateTextView.setTextColor(Color.WHITE);
+ mUpdateRateTextView.setTextColor(Color.WHITE);
+ mDrawCountTextView.setTextColor(Color.WHITE);
+
+ mTimeTextView.getPaint().setAntiAlias(false);
+ mTimeStampTextView.getPaint().setAntiAlias(false);
+ mStateTextView.getPaint().setAntiAlias(false);
+ mUpdateRateTextView.getPaint().setAntiAlias(false);
+ mDrawCountTextView.getPaint().setAntiAlias(false);
+
+ refreshDisplayAndSetNextUpdate();
+ }
+
+ /**
+ * Updates UI in Ambient view (once a minute). Because we need to update UI sooner than that
+ * (every ~20 seconds), we also use an Alarm. However, since the processor is awake for this
+ * callback, we might as well call refreshDisplayAndSetNextUpdate() to update screen and reset
+ * the Alarm.
+ *
+ * If you are happy with just updating the screen once a minute in Ambient Mode (which will be
+ * the case a majority of the time), then you can just use this method and remove all
+ * references/code regarding Alarms.
+ */
+ @Override
+ public void onUpdateAmbient() {
+ Log.d(TAG, "onUpdateAmbient()");
+ super.onUpdateAmbient();
+
+ refreshDisplayAndSetNextUpdate();
+ }
+
+ /**
+ * Prepares UI for Active view (non-Ambient).
+ */
+ @Override
+ public void onExitAmbient() {
+ Log.d(TAG, "onExitAmbient()");
+ super.onExitAmbient();
+
+ /** Clears out Alarms since they are only used in ambient mode. */
+ mAmbientStateAlarmManager.cancel(mAmbientStatePendingIntent);
+
+ mStateTextView.setTextColor(Color.GREEN);
+ mUpdateRateTextView.setTextColor(Color.GREEN);
+ mDrawCountTextView.setTextColor(Color.GREEN);
+
+ mTimeTextView.getPaint().setAntiAlias(true);
+ mTimeStampTextView.getPaint().setAntiAlias(true);
+ mStateTextView.getPaint().setAntiAlias(true);
+ mUpdateRateTextView.getPaint().setAntiAlias(true);
+ mDrawCountTextView.getPaint().setAntiAlias(true);
+
+ refreshDisplayAndSetNextUpdate();
+ }
+
+ /**
+ * Loads data/updates screen (via method), but most importantly, sets up the next refresh
+ * (active mode = Handler and ambient mode = Alarm).
+ */
+ private void refreshDisplayAndSetNextUpdate() {
+
+ loadDataAndUpdateScreen();
+
+ long timeMs = System.currentTimeMillis();
+
+ if (isAmbient()) {
+ /** Calculate next trigger time (based on state). */
+ long delayMs = AMBIENT_INTERVAL_MS - (timeMs % AMBIENT_INTERVAL_MS);
+ long triggerTimeMs = timeMs + delayMs;
+
+ /**
+ * Note: Make sure you have set activity launchMode to singleInstance in the manifest.
+ * Otherwise, it is easy for the AlarmManager launch intent to open a new activity
+ * every time the Alarm is triggered rather than reusing this Activity.
+ */
+ mAmbientStateAlarmManager.setExact(
+ AlarmManager.RTC_WAKEUP,
+ triggerTimeMs,
+ mAmbientStatePendingIntent);
+
+ } else {
+ /** Calculate next trigger time (based on state). */
+ long delayMs = ACTIVE_INTERVAL_MS - (timeMs % ACTIVE_INTERVAL_MS);
+
+ mActiveModeUpdateHandler.removeMessages(MSG_UPDATE_SCREEN);
+ mActiveModeUpdateHandler.sendEmptyMessageDelayed(MSG_UPDATE_SCREEN, delayMs);
+ }
+ }
+
+ /**
+ * Updates display based on Ambient state. If you need to pull data, you should do it here.
+ */
+ private void loadDataAndUpdateScreen() {
+
+ mDrawCount += 1;
+ long currentTimeMs = System.currentTimeMillis();
+ Log.d(TAG, "loadDataAndUpdateScreen(): " + currentTimeMs + "(" + isAmbient() + ")");
+
+ if (isAmbient()) {
+
+ mTimeTextView.setText(sDateFormat.format(new Date()));
+ mTimeStampTextView.setText(getString(R.string.timestamp_label, currentTimeMs));
+
+ mStateTextView.setText(getString(R.string.mode_ambient_label));
+ mUpdateRateTextView.setText(
+ getString(R.string.update_rate_label, (AMBIENT_INTERVAL_MS / 1000)));
+
+ mDrawCountTextView.setText(getString(R.string.draw_count_label, mDrawCount));
+
+ } else {
+ mTimeTextView.setText(sDateFormat.format(new Date()));
+ mTimeStampTextView.setText(getString(R.string.timestamp_label, currentTimeMs));
+
+ mStateTextView.setText(getString(R.string.mode_active_label));
+ mUpdateRateTextView.setText(
+ getString(R.string.update_rate_label, (ACTIVE_INTERVAL_MS / 1000)));
+
+ mDrawCountTextView.setText(getString(R.string.draw_count_label, mDrawCount));
+ }
+ }
+
+ /**
+ * Handler separated into static class to avoid memory leaks.
+ */
+ private static class UpdateHandler extends Handler {
+ private final WeakReference<MainActivity> mMainActivityWeakReference;
+
+ public UpdateHandler(MainActivity reference) {
+ mMainActivityWeakReference = new WeakReference<MainActivity>(reference);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ MainActivity mainActivity = mMainActivityWeakReference.get();
+
+ if (mainActivity != null) {
+ switch (message.what) {
+ case MSG_UPDATE_SCREEN:
+ mainActivity.refreshDisplayAndSetNextUpdate();
+ break;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/wearable/wear/AlwaysOn/Wearable/src/main/res/layout/activity_main.xml b/wearable/wear/AlwaysOn/Wearable/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..d808e6b
--- /dev/null
+++ b/wearable/wear/AlwaysOn/Wearable/src/main/res/layout/activity_main.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2015 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.wearable.view.WatchViewStub
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/watch_view_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:rectLayout="@layout/rect_activity_main"
+ app:roundLayout="@layout/round_activity_main"
+ tools:context=".MainActivity"
+ tools:deviceIds="wear">
+</android.support.wearable.view.WatchViewStub>
diff --git a/wearable/wear/AlwaysOn/Wearable/src/main/res/layout/rect_activity_main.xml b/wearable/wear/AlwaysOn/Wearable/src/main/res/layout/rect_activity_main.xml
new file mode 100644
index 0000000..bfb8147
--- /dev/null
+++ b/wearable/wear/AlwaysOn/Wearable/src/main/res/layout/rect_activity_main.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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:paddingTop="@dimen/square_top_margin"
+ android:paddingLeft="@dimen/square_left_margin"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".MainActivity"
+ tools:deviceIds="wear_square">
+
+ <TextView
+ android:id="@+id/time"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="24sp"
+ android:text="Hello, time!"/>
+
+ <TextView
+ android:id="@+id/time_stamp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Hello, timestamp!"/>
+
+ <TextView
+ android:id="@+id/state"
+ android:textColor="@color/green"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Hello, state!"/>
+
+ <TextView
+ android:id="@+id/update_rate"
+ android:textColor="@color/green"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Hello, update rate!"/>
+
+ <TextView
+ android:id="@+id/draw_count"
+ android:textColor="@color/green"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Hello, draw count!"/>
+</LinearLayout>
diff --git a/wearable/wear/AlwaysOn/Wearable/src/main/res/layout/round_activity_main.xml b/wearable/wear/AlwaysOn/Wearable/src/main/res/layout/round_activity_main.xml
new file mode 100644
index 0000000..8fa7a2d
--- /dev/null
+++ b/wearable/wear/AlwaysOn/Wearable/src/main/res/layout/round_activity_main.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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:paddingTop="@dimen/round_top_margin"
+ android:paddingLeft="@dimen/round_left_margin"
+ android:layout_width="match_parent"
+ android:orientation="vertical"
+ android:layout_height="match_parent"
+ tools:context=".MainActivity"
+ tools:deviceIds="wear_round">
+
+ <TextView
+ android:id="@+id/time"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="24sp"
+ android:text="Hello, time!"/>
+
+ <TextView
+ android:id="@+id/time_stamp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Hello, timestamp!"/>
+
+ <TextView
+ android:id="@+id/state"
+ android:textColor="@color/green"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Hello, state!"/>
+
+ <TextView
+ android:id="@+id/update_rate"
+ android:textColor="@color/green"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Hello, update rate!"/>
+
+ <TextView
+ android:id="@+id/draw_count"
+ android:textColor="@color/green"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Hello, draw count!"/>
+</LinearLayout>
diff --git a/wearable/wear/AlwaysOn/Wearable/src/main/res/values/dimens.xml b/wearable/wear/AlwaysOn/Wearable/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..d44096a
--- /dev/null
+++ b/wearable/wear/AlwaysOn/Wearable/src/main/res/values/dimens.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="square_top_margin">24dp</dimen>
+ <dimen name="square_left_margin">16dp</dimen>
+
+ <dimen name="round_top_margin">34dp</dimen>
+ <dimen name="round_left_margin">34dp</dimen>
+</resources>
diff --git a/wearable/wear/AlwaysOn/Wearable/src/main/res/values/strings.xml b/wearable/wear/AlwaysOn/Wearable/src/main/res/values/strings.xml
new file mode 100644
index 0000000..7d4c2f6
--- /dev/null
+++ b/wearable/wear/AlwaysOn/Wearable/src/main/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">Always On Example</string>
+ <string name="timestamp_label">Timestamp: %1$d</string>
+ <string name="mode_active_label">Active Mode (Handler)</string>
+ <string name="mode_ambient_label">Ambient Mode (Alarm)</string>
+ <string name="update_rate_label">Update rate: %1$d sec</string>
+ <string name="draw_count_label">Draw count: %1$d</string>
+</resources>
diff --git a/wearable/wear/AlwaysOn/build.gradle b/wearable/wear/AlwaysOn/build.gradle
new file mode 100644
index 0000000..18f393f
--- /dev/null
+++ b/wearable/wear/AlwaysOn/build.gradle
@@ -0,0 +1,11 @@
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+ pathToBuild "../../../../../build"
+ pathToSamplesCommon "../../../common"
+}
+apply from: "../../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/wearable/wear/AlwaysOn/buildSrc/build.gradle b/wearable/wear/AlwaysOn/buildSrc/build.gradle
new file mode 100644
index 0000000..7cebf71
--- /dev/null
+++ b/wearable/wear/AlwaysOn/buildSrc/build.gradle
@@ -0,0 +1,15 @@
+repositories {
+ mavenCentral()
+}
+dependencies {
+ compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+ main {
+ groovy {
+ srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
+ }
+ }
+}
+
diff --git a/wearable/wear/AlwaysOn/gradle/wrapper/gradle-wrapper.jar b/wearable/wear/AlwaysOn/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/wearable/wear/AlwaysOn/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/wearable/wear/AlwaysOn/gradle/wrapper/gradle-wrapper.properties b/wearable/wear/AlwaysOn/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..7d3b483
--- /dev/null
+++ b/wearable/wear/AlwaysOn/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Dec 22 11:24:44 EST 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/wearable/wear/AlwaysOn/gradlew b/wearable/wear/AlwaysOn/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/wearable/wear/AlwaysOn/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/wearable/wear/AlwaysOn/gradlew.bat b/wearable/wear/AlwaysOn/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/wearable/wear/AlwaysOn/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/wearable/wear/AlwaysOn/screenshots/1-main-active.png b/wearable/wear/AlwaysOn/screenshots/1-main-active.png
new file mode 100644
index 0000000..cf5db84
--- /dev/null
+++ b/wearable/wear/AlwaysOn/screenshots/1-main-active.png
Binary files differ
diff --git a/wearable/wear/AlwaysOn/screenshots/2-main-ambient.png b/wearable/wear/AlwaysOn/screenshots/2-main-ambient.png
new file mode 100644
index 0000000..b871fce
--- /dev/null
+++ b/wearable/wear/AlwaysOn/screenshots/2-main-ambient.png
Binary files differ
diff --git a/wearable/wear/AlwaysOn/screenshots/3-main-active-round.png b/wearable/wear/AlwaysOn/screenshots/3-main-active-round.png
new file mode 100644
index 0000000..6acb920
--- /dev/null
+++ b/wearable/wear/AlwaysOn/screenshots/3-main-active-round.png
Binary files differ
diff --git a/wearable/wear/AlwaysOn/screenshots/4-main-ambient-round.png b/wearable/wear/AlwaysOn/screenshots/4-main-ambient-round.png
new file mode 100644
index 0000000..2a56f89
--- /dev/null
+++ b/wearable/wear/AlwaysOn/screenshots/4-main-ambient-round.png
Binary files differ
diff --git a/wearable/wear/AlwaysOn/template-params.xml b/wearable/wear/AlwaysOn/template-params.xml
new file mode 100644
index 0000000..e5794ee
--- /dev/null
+++ b/wearable/wear/AlwaysOn/template-params.xml
@@ -0,0 +1,82 @@
+<?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.
+-->
+<sample>
+ <name>AlwaysOn</name>
+ <group>Wearable</group>
+ <package>com.example.android.wearable.wear.alwayson</package>
+
+ <dependency_wearable>com.google.android.support:wearable:1.2.0</dependency_wearable>
+ <provided_dependency_wearable>com.google.android.wearable:wearable:1.0.0</provided_dependency_wearable>
+
+ <minSdk>20</minSdk>
+ <targetSdkVersion>22</targetSdkVersion>
+
+ <strings>
+ <intro>
+ <![CDATA[
+ Demonstrates a native Android Wear app using ambient screen support.
+ ]]>>
+ </intro>
+ </strings>
+
+ <template src="base-build" />
+ <template src="Wear" />
+
+ <!-- Include common code modules by uncommenting them below. -->
+ <!-- common src="logger"/ -->
+
+ <metadata>
+ <status>PUBLISHED</status>
+ <categories>Wearable</categories>
+ <technologies>Android</technologies>
+ <languages>Java</languages>
+ <solutions>Mobile</solutions>
+ <level>INTERMEDIATE</level>
+ <screenshots>
+ <img>screenshots/1-main-active.png</img>
+ <img>screenshots/2-main-ambient.png</img>
+ <img>screenshots/3-main-active-round.png</img>
+ <img>screenshots/4-main-ambient-round.png</img>
+ </screenshots>
+ <api_refs>
+ <android>android.support.wearable.activity.WearableActivity</android>
+ </api_refs>
+ <description>
+ <![CDATA[
+A basic sample showing how to support ambient mode for native Android Wear apps.
+]]>
+ </description>
+
+ <intro>
+ <![CDATA[
+[WearableActivity][1] offers methods for supporting your native app staying on the screen when the Wear device enters ambient mode.
+
+This example extends [WearableActivity][1] and overrides onEnterAmbient, onUpdateAmbient, and onExitAmbient to allow the simple native Wear app to support ambient mode.
+
+In ambient mode, this app follows best practices by keeping most pixels black, avoiding large blocks of white pixels, using only black and white, and disabling anti-aliasing (following the [design guidelines for Watch Faces][2]).
+
+In addition and most importantly, the app sleeps while in ambient mode for 20 seconds between any updates to conserving battery life (processor allowed to sleep). If you can hold off on updates for a full minute, you can throw away the Handler and just use onUpdateAmbient to save even more battery life.
+
+As always, you will still want to apply the [performance guidelines][3] outlined in the Watch Faces documention to your app.
+
+[1]: http://developer.android.com/reference/android/support/wearable/activity/WearableActivity.html
+[2]: https://developer.android.com/training/wearables/watch-faces/designing.html#DesignGuidelines
+[3]: https://developer.android.com/training/wearables/watch-faces/performance.html
+]]>
+ </intro>
+ </metadata>
+</sample>
\ No newline at end of file
diff --git a/wearable/wear/Geofencing/Application/src/main/java/com/example/android/wearable/geofencing/MainActivity.java b/wearable/wear/Geofencing/Application/src/main/java/com/example/android/wearable/geofencing/MainActivity.java
index 350c9c5..7564e6c 100644
--- a/wearable/wear/Geofencing/Application/src/main/java/com/example/android/wearable/geofencing/MainActivity.java
+++ b/wearable/wear/Geofencing/Application/src/main/java/com/example/android/wearable/geofencing/MainActivity.java
@@ -37,7 +37,7 @@
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
+import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
@@ -48,7 +48,7 @@
import java.util.List;
public class MainActivity extends Activity implements ConnectionCallbacks,
- OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks {
+ OnConnectionFailedListener {
// Internal List of Geofence objects. In a real app, these might be provided by an API based on
// locations within the user's proximity.
@@ -143,10 +143,6 @@
}
}
- @Override
- public void onDisconnected() {
- }
-
/**
* Once the connection is available, send a request to add the Geofences.
*/
diff --git a/wearable/wear/Geofencing/template-params.xml b/wearable/wear/Geofencing/template-params.xml
index 6e66ecc..66bfcc0 100644
--- a/wearable/wear/Geofencing/template-params.xml
+++ b/wearable/wear/Geofencing/template-params.xml
@@ -23,7 +23,7 @@
<package>com.example.android.wearable.geofencing</package>
<minSdk>18</minSdk>
- <dependency>com.google.android.gms:play-services-location:6.5.+</dependency>
+ <dependency>com.google.android.gms:play-services-location:7.3.0</dependency>
<wearable>
<has_handheld_app>true</has_handheld_app>
diff --git a/wearable/wear/SpeedTracker/template-params.xml b/wearable/wear/SpeedTracker/template-params.xml
index 6287a2a..71c5ac2 100644
--- a/wearable/wear/SpeedTracker/template-params.xml
+++ b/wearable/wear/SpeedTracker/template-params.xml
@@ -28,8 +28,8 @@
<has_handheld_app>true</has_handheld_app>
</wearable>
- <dependency>com.google.android.gms:play-services-location:6.5.+</dependency>
- <dependency_wearable>com.google.android.gms:play-services-location:6.5.+</dependency_wearable>
+ <dependency>com.google.android.gms:play-services-location:7.3.0</dependency>
+ <dependency_wearable>com.google.android.gms:play-services-location:7.3.0</dependency_wearable>
<strings>
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/AnalogWatchFaceService.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/AnalogWatchFaceService.java
index f0fb4f5..15c550f 100644
--- a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/AnalogWatchFaceService.java
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/AnalogWatchFaceService.java
@@ -33,10 +33,10 @@
import android.support.wearable.watchface.CanvasWatchFaceService;
import android.support.wearable.watchface.WatchFaceService;
import android.support.wearable.watchface.WatchFaceStyle;
-import android.text.format.Time;
import android.util.Log;
import android.view.SurfaceHolder;
+import java.util.Calendar;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
@@ -64,12 +64,14 @@
private class Engine extends CanvasWatchFaceService.Engine {
static final int MSG_UPDATE_TIME = 0;
+ static final float TWO_PI = (float) Math.PI * 2f;
+
Paint mHourPaint;
Paint mMinutePaint;
Paint mSecondPaint;
Paint mTickPaint;
boolean mMute;
- Time mTime;
+ Calendar mCalendar;
/** Handler to update the time once a second in interactive mode. */
final Handler mUpdateTimeHandler = new Handler() {
@@ -95,8 +97,8 @@
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mTime.clear(intent.getStringExtra("time-zone"));
- mTime.setToNow();
+ mCalendar.setTimeZone(TimeZone.getDefault());
+ invalidate();
}
};
boolean mRegisteredTimeZoneReceiver = false;
@@ -124,7 +126,7 @@
.build());
Resources resources = AnalogWatchFaceService.this.getResources();
- Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
+ Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg, null /* theme */);
mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
mHourPaint = new Paint();
@@ -150,7 +152,7 @@
mTickPaint.setStrokeWidth(2.f);
mTickPaint.setAntiAlias(true);
- mTime = new Time();
+ mCalendar = Calendar.getInstance();
}
@Override
@@ -212,7 +214,7 @@
@Override
public void onDraw(Canvas canvas, Rect bounds) {
- mTime.setToNow();
+ mCalendar.setTimeInMillis(System.currentTimeMillis());
int width = bounds.width();
int height = bounds.height();
@@ -236,7 +238,7 @@
float innerTickRadius = centerX - 10;
float outerTickRadius = centerX;
for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
- float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
+ float tickRot = tickIndex * TWO_PI / 12;
float innerX = (float) Math.sin(tickRot) * innerTickRadius;
float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
float outerX = (float) Math.sin(tickRot) * outerTickRadius;
@@ -245,10 +247,13 @@
centerX + outerX, centerY + outerY, mTickPaint);
}
- float secRot = mTime.second / 30f * (float) Math.PI;
- int minutes = mTime.minute;
- float minRot = minutes / 30f * (float) Math.PI;
- float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;
+ float seconds =
+ mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f;
+ float secRot = seconds / 60f * TWO_PI;
+ float minutes = mCalendar.get(Calendar.MINUTE) + seconds / 60f;
+ float minRot = minutes / 60f * TWO_PI;
+ float hours = mCalendar.get(Calendar.HOUR) + minutes / 60f;
+ float hrRot = hours / 12f * TWO_PI;
float secLength = centerX - 20;
float minLength = centerX - 40;
@@ -280,8 +285,7 @@
registerReceiver();
// Update time zone in case it changed while we weren't visible.
- mTime.clear(TimeZone.getDefault().getID());
- mTime.setToNow();
+ mCalendar.setTimeZone(TimeZone.getDefault());
} else {
unregisterReceiver();
}
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceService.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceService.java
index b8b1314..0bc420d 100644
--- a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceService.java
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceService.java
@@ -31,7 +31,7 @@
import android.support.wearable.watchface.CanvasWatchFaceService;
import android.support.wearable.watchface.WatchFaceService;
import android.support.wearable.watchface.WatchFaceStyle;
-import android.text.format.Time;
+import android.text.format.DateFormat;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.WindowInsets;
@@ -46,6 +46,7 @@
import com.google.android.gms.wearable.DataMapItem;
import com.google.android.gms.wearable.Wearable;
+import java.util.Calendar;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
@@ -125,8 +126,8 @@
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mTime.clear(intent.getStringExtra("time-zone"));
- mTime.setToNow();
+ mCalendar.setTimeZone(TimeZone.getDefault());
+ invalidate();
}
};
boolean mRegisteredTimeZoneReceiver = false;
@@ -139,7 +140,7 @@
Paint mColonPaint;
float mColonWidth;
boolean mMute;
- Time mTime;
+ Calendar mCalendar;
boolean mShouldDrawColons;
float mXOffset;
float mYOffset;
@@ -185,7 +186,7 @@
mAmPmPaint = createTextPaint(resources.getColor(R.color.digital_am_pm));
mColonPaint = createTextPaint(resources.getColor(R.color.digital_colons));
- mTime = new Time();
+ mCalendar = Calendar.getInstance();
}
@Override
@@ -219,8 +220,7 @@
registerReceiver();
// Update time zone in case it changed while we weren't visible.
- mTime.clear(TimeZone.getDefault().getID());
- mTime.setToNow();
+ mCalendar.setTimeZone(TimeZone.getDefault());
} else {
unregisterReceiver();
@@ -403,18 +403,14 @@
return String.format("%02d", hour);
}
- private int convertTo12Hour(int hour) {
- int result = hour % 12;
- return (result == 0) ? 12 : result;
- }
-
- private String getAmPmString(int hour) {
- return (hour < 12) ? mAmString : mPmString;
+ private String getAmPmString(int amPm) {
+ return amPm == Calendar.AM ? mAmString : mPmString;
}
@Override
public void onDraw(Canvas canvas, Rect bounds) {
- mTime.setToNow();
+ mCalendar.setTimeInMillis(System.currentTimeMillis());
+ boolean is24Hour = DateFormat.is24HourFormat(DigitalWatchFaceService.this);
// Show colons for the first half of each second so the colons blink on when the time
// updates.
@@ -425,7 +421,19 @@
// Draw the hours.
float x = mXOffset;
- String hourString = String.valueOf(convertTo12Hour(mTime.hour));
+ String hourString;
+ if (is24Hour) {
+ hourString = formatTwoDigitNumber(mCalendar.get(Calendar.HOUR_OF_DAY));
+ } else {
+ int hour = mCalendar.get(Calendar.HOUR);
+ if (hour == 0) {
+ hour = 12;
+ }
+ hourString = String.valueOf(hour);
+ if (hour < 10) {
+ x += mHourPaint.measureText("0");
+ }
+ }
canvas.drawText(hourString, x, mYOffset, mHourPaint);
x += mHourPaint.measureText(hourString);
@@ -437,22 +445,23 @@
x += mColonWidth;
// Draw the minutes.
- String minuteString = formatTwoDigitNumber(mTime.minute);
+ String minuteString = formatTwoDigitNumber(mCalendar.get(Calendar.MINUTE));
canvas.drawText(minuteString, x, mYOffset, mMinutePaint);
x += mMinutePaint.measureText(minuteString);
- // In ambient and mute modes, draw AM/PM. Otherwise, draw a second blinking
- // colon followed by the seconds.
- if (isInAmbientMode() || mMute) {
- x += mColonWidth;
- canvas.drawText(getAmPmString(mTime.hour), x, mYOffset, mAmPmPaint);
- } else {
+ // In unmuted interactive mode, draw a second blinking colon followed by the seconds.
+ // Otherwise, if we're in 12-hour mode, draw AM/PM
+ if (!isInAmbientMode() && !mMute) {
if (mShouldDrawColons) {
canvas.drawText(COLON_STRING, x, mYOffset, mColonPaint);
}
x += mColonWidth;
- canvas.drawText(formatTwoDigitNumber(mTime.second), x, mYOffset,
- mSecondPaint);
+ canvas.drawText(formatTwoDigitNumber(
+ mCalendar.get(Calendar.SECOND)), x, mYOffset, mSecondPaint);
+ } else if (!is24Hour) {
+ x += mColonWidth;
+ canvas.drawText(getAmPmString(
+ mCalendar.get(Calendar.AM_PM)), x, mYOffset, mAmPmPaint);
}
}
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/SweepWatchFaceService.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/SweepWatchFaceService.java
index 44e9569..193f29a 100644
--- a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/SweepWatchFaceService.java
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/SweepWatchFaceService.java
@@ -31,10 +31,10 @@
import android.support.wearable.watchface.CanvasWatchFaceService;
import android.support.wearable.watchface.WatchFaceService;
import android.support.wearable.watchface.WatchFaceStyle;
-import android.text.format.Time;
import android.util.Log;
import android.view.SurfaceHolder;
+import java.util.Calendar;
import java.util.TimeZone;
/**
@@ -53,18 +53,20 @@
}
private class Engine extends CanvasWatchFaceService.Engine {
+ static final float TWO_PI = (float) Math.PI * 2f;
+
Paint mHourPaint;
Paint mMinutePaint;
Paint mSecondPaint;
Paint mTickPaint;
boolean mMute;
- Time mTime;
+ Calendar mCalendar;
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mTime.clear(intent.getStringExtra("time-zone"));
- mTime.setToNow();
+ mCalendar.setTimeZone(TimeZone.getDefault());
+ invalidate();
}
};
boolean mRegisteredTimeZoneReceiver = false;
@@ -92,7 +94,7 @@
.build());
Resources resources = SweepWatchFaceService.this.getResources();
- Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
+ Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg, null /* theme */);
mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
mHourPaint = new Paint();
@@ -118,7 +120,7 @@
mTickPaint.setStrokeWidth(2.f);
mTickPaint.setAntiAlias(true);
- mTime = new Time();
+ mCalendar = Calendar.getInstance();
}
@Override
@@ -174,8 +176,7 @@
Log.v(TAG, "onDraw");
}
long now = System.currentTimeMillis();
- mTime.set(now);
- int milliseconds = (int) (now % 1000);
+ mCalendar.setTimeInMillis(now);
int width = bounds.width();
int height = bounds.height();
@@ -208,11 +209,13 @@
centerX + outerX, centerY + outerY, mTickPaint);
}
- float seconds = mTime.second + milliseconds / 1000f;
- float secRot = seconds / 30f * (float) Math.PI;
- int minutes = mTime.minute;
- float minRot = minutes / 30f * (float) Math.PI;
- float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;
+ float seconds =
+ mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f;
+ float secRot = seconds / 60f * TWO_PI;
+ float minutes = mCalendar.get(Calendar.MINUTE) + seconds / 60f;
+ float minRot = minutes / 60f * TWO_PI;
+ float hours = mCalendar.get(Calendar.HOUR) + minutes / 60f;
+ float hrRot = hours / 12f * TWO_PI;
float secLength = centerX - 20;
float minLength = centerX - 40;
@@ -246,8 +249,7 @@
registerReceiver();
// Update time zone in case it changed while we weren't visible.
- mTime.clear(TimeZone.getDefault().getID());
- mTime.setToNow();
+ mCalendar.setTimeZone(TimeZone.getDefault());
invalidate();
} else {
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/TiltWatchFaceService.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/TiltWatchFaceService.java
index 6dd01b0..ffb0b6c 100644
--- a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/TiltWatchFaceService.java
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/TiltWatchFaceService.java
@@ -24,11 +24,11 @@
import android.opengl.Matrix;
import android.support.wearable.watchface.Gles2WatchFaceService;
import android.support.wearable.watchface.WatchFaceStyle;
-import android.text.format.Time;
import android.util.Log;
import android.view.Gravity;
import android.view.SurfaceHolder;
+import java.util.Calendar;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
@@ -44,6 +44,9 @@
/** Expected frame rate in interactive mode. */
private static final long FPS = 60;
+ /** Z distance from the camera to the watchface. */
+ private static final float EYE_Z = 2.3f;
+
/** How long each frame is displayed at expected frame rate. */
private static final long FRAME_PERIOD_MS = TimeUnit.SECONDS.toMillis(1) / FPS;
@@ -107,7 +110,7 @@
/** Triangle for the hour hand. */
private Gles2ColoredTriangleList mHourHandTriangle;
- private Time mTime = new Time();
+ private Calendar mCalendar = Calendar.getInstance();
/** Whether we've registered {@link #mTimeZoneReceiver}. */
private boolean mRegisteredTimeZoneReceiver;
@@ -115,8 +118,8 @@
private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mTime.clear(intent.getStringExtra("time-zone"));
- mTime.setToNow();
+ mCalendar.setTimeZone(TimeZone.getDefault());
+ invalidate();
}
};
@@ -170,7 +173,7 @@
mMinuteHandTriangle = createHand(
triangleProgram,
0.06f /* width */,
- 0.8f /* height */,
+ 1f /* height */,
new float[]{
0.7f /* red */,
0.7f /* green */,
@@ -181,7 +184,7 @@
mHourHandTriangle = createHand(
triangleProgram,
0.1f /* width */,
- 0.5f /* height */,
+ 0.6f /* height */,
new float[]{
0.9f /* red */,
0.9f /* green */,
@@ -204,14 +207,14 @@
final float eyeY = (float) Math.sin(cameraAngle);
Matrix.setLookAtM(mViewMatrices[i],
0, // dest index
- eyeX, eyeY, -3, // eye
+ eyeX, eyeY, EYE_Z, // eye
0, 0, 0, // center
0, 1, 0); // up vector
}
Matrix.setLookAtM(mAmbientViewMatrix,
0, // dest index
- 0, 0, -3, // eye
+ 0, 0, EYE_Z, // eye
0, 0, 0, // center
0, 1, 0); // up vector
}
@@ -378,8 +381,7 @@
registerReceiver();
// Update time zone in case it changed while we were detached.
- mTime.clear(TimeZone.getDefault().getID());
- mTime.setToNow();
+ mCalendar.setTimeZone(TimeZone.getDefault());
invalidate();
} else {
@@ -436,26 +438,28 @@
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// Compute angle indices for the three hands.
- mTime.setToNow();
- final int secIndex = mTime.second * 360 / 60;
- final int minIndex = mTime.minute * 360 / 60;
- final int hoursIndex = (mTime.hour % 12) * 360 / 12 + mTime.minute * 360 / 60 / 12;
+ mCalendar.setTimeInMillis(System.currentTimeMillis());
+ float seconds =
+ mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f;
+ float minutes = mCalendar.get(Calendar.MINUTE) + seconds / 60f;
+ float hours = mCalendar.get(Calendar.HOUR) + minutes / 60f;
+ final int secIndex = (int) (seconds / 60f * 360f);
+ final int minIndex = (int) (minutes / 60f * 360f);
+ final int hoursIndex = (int) (hours / 12f * 360f);
// Draw triangles from back to front. Don't draw the second hand in ambient mode.
- {
- // Combine the model matrix with the projection and camera view.
- Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[hoursIndex], 0);
- // Draw the triangle.
- mHourHandTriangle.draw(mMvpMatrix);
- }
- {
- // Combine the model matrix with the projection and camera view.
- Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[minIndex], 0);
+ // Combine the model matrix with the projection and camera view.
+ Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[hoursIndex], 0);
- // Draw the triangle.
- mMinuteHandTriangle.draw(mMvpMatrix);
- }
+ // Draw the triangle.
+ mHourHandTriangle.draw(mMvpMatrix);
+
+ // Combine the model matrix with the projection and camera view.
+ Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[minIndex], 0);
+
+ // Draw the triangle.
+ mMinuteHandTriangle.draw(mMvpMatrix);
if (!isInAmbientMode()) {
// Combine the model matrix with the projection and camera view.
Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[secIndex], 0);
@@ -463,11 +467,10 @@
// Draw the triangle.
mSecondHandTriangle.draw(mMvpMatrix);
}
- {
- // Draw the major and minor ticks.
- mMajorTickTriangles.draw(vpMatrix);
- mMinorTickTriangles.draw(vpMatrix);
- }
+
+ // Draw the major and minor ticks.
+ mMajorTickTriangles.draw(vpMatrix);
+ mMinorTickTriangles.draw(vpMatrix);
// Draw every frame as long as we're visible and in interactive mode.
if (isVisible() && !isInAmbientMode()) {
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/AndroidManifest.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/AndroidManifest.xml
index 5f330d5..76f0198 100644
--- a/wearable/wear/XYZTouristAttractions/Application/src/main/AndroidManifest.xml
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/AndroidManifest.xml
@@ -58,6 +58,10 @@
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
+ <meta-data
+ android:name="com.example.android.xyztouristattractions.config.GlideConfiguration"
+ android:value="GlideModule"/>
+
</application>
</manifest>
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/config/GlideConfiguration.java b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/config/GlideConfiguration.java
new file mode 100644
index 0000000..77d6fc8
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/config/GlideConfiguration.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.config;
+
+import android.content.Context;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.GlideBuilder;
+import com.bumptech.glide.load.DecodeFormat;
+import com.bumptech.glide.module.GlideModule;
+
+/**
+ * This allows global overriding of some default Glide configuration values.
+ * For additional information see the Glide docs:
+ * https://github.com/bumptech/glide/wiki/Configuration
+ */
+public class GlideConfiguration implements GlideModule {
+ @Override
+ public void applyOptions(Context context, GlideBuilder builder) {
+ // Set Glide decode format to the higher quality ARGB_8888 format
+ builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
+ }
+
+ @Override
+ public void registerComponents(Context context, Glide glide) {
+
+ }
+}
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/AttractionListActivity.java b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/AttractionListActivity.java
index 8d43112..8d2908c 100644
--- a/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/AttractionListActivity.java
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/AttractionListActivity.java
@@ -16,9 +16,9 @@
package com.example.android.xyztouristattractions.ui;
-import android.app.AlertDialog;
import android.os.Bundle;
-import android.support.v7.app.ActionBarActivity;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
@@ -31,7 +31,7 @@
* The main tourist attraction activity screen which contains a list of
* attractions sorted by distance.
*/
-public class AttractionListActivity extends ActionBarActivity {
+public class AttractionListActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/DetailActivity.java b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/DetailActivity.java
index a83f480..3950785 100644
--- a/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/DetailActivity.java
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/DetailActivity.java
@@ -24,7 +24,7 @@
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
-import android.support.v7.app.ActionBarActivity;
+import android.support.v7.app.AppCompatActivity;
import android.view.View;
import com.example.android.xyztouristattractions.R;
@@ -33,7 +33,7 @@
* The tourist attraction detail activity screen which contains the details of
* a single attraction.
*/
-public class DetailActivity extends ActionBarActivity {
+public class DetailActivity extends AppCompatActivity {
private static final String EXTRA_ATTRACTION = "attraction";
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/colors.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/colors.xml
index 142f548..2c16a5d 100644
--- a/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/colors.xml
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/colors.xml
@@ -17,10 +17,6 @@
<resources>
- <color name="colorPrimary">#4e6cef</color>
- <color name="colorPrimaryDark">#2a36b1</color>
- <color name="colorAccent">#ff7043</color>
-
<color name="text_background">#90000000</color>
<color name="transparent_actionbar_background">#22000000</color>
<color name="lighter_gray">#ddd</color>
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/color/action_color.xml b/wearable/wear/XYZTouristAttractions/Shared/src/main/res/values/colors.xml
similarity index 73%
rename from wearable/wear/XYZTouristAttractions/Wearable/src/main/res/color/action_color.xml
rename to wearable/wear/XYZTouristAttractions/Shared/src/main/res/values/colors.xml
index 634d806..73c2b6c 100644
--- a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/color/action_color.xml
+++ b/wearable/wear/XYZTouristAttractions/Shared/src/main/res/values/colors.xml
@@ -15,11 +15,10 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
+<resources>
- <item android:state_pressed="true"
- android:color="#ee3c4b90" /> <!-- pressed -->
+ <color name="colorPrimary">#4e6cef</color>
+ <color name="colorPrimaryDark">#2a36b1</color>
+ <color name="colorAccent">#ff7043</color>
- <item android:color="#ee5c6bc0" /> <!-- default -->
-
-</selector>
\ No newline at end of file
+</resources>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsActivity.java b/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsActivity.java
index 464eb8a..64d28bc 100644
--- a/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsActivity.java
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsActivity.java
@@ -84,6 +84,8 @@
mAdapter = new AttractionsGridPagerAdapter(this, mAttractions);
mAdapter.setOnChromeFadeListener(this);
mGridViewPager.setAdapter(mAdapter);
+ mDotsPageIndicator.setPager(mGridViewPager);
+ mDotsPageIndicator.setOnPageChangeListener(mAdapter);
topFrameLayout.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
@Override
@@ -243,8 +245,6 @@
// Update UI based on the result of the background processing
mAdapter.setData(result);
mAdapter.notifyDataSetChanged();
- mDotsPageIndicator.setPager(mGridViewPager);
- mDotsPageIndicator.setOnPageChangeListener(mAdapter);
mProgressBar.setVisibility(View.GONE);
mDotsPageIndicator.setVisibility(View.VISIBLE);
mGridViewPager.setVisibility(View.VISIBLE);
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsGridPagerAdapter.java b/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsGridPagerAdapter.java
index 99737f4..97accde 100644
--- a/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsGridPagerAdapter.java
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsGridPagerAdapter.java
@@ -25,11 +25,11 @@
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.support.wearable.activity.ConfirmationActivity;
+import android.support.wearable.view.ActionPage;
import android.support.wearable.view.CardFrame;
import android.support.wearable.view.CardScrollView;
import android.support.wearable.view.GridPagerAdapter;
import android.support.wearable.view.GridViewPager;
-import android.support.wearable.view.WatchViewStub;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -97,7 +97,7 @@
}
@Override
- protected Object instantiateItem(ViewGroup container, int row, final int column) {
+ public Object instantiateItem(ViewGroup container, int row, final int column) {
if (mAttractions != null && mAttractions.size() > 0) {
final Attraction attraction = mAttractions.get(row);
switch (column) {
@@ -150,48 +150,30 @@
return cardScrollView;
case PAGER_NAVIGATE_ACTION_COLUMN:
// The navigate action
- final WatchViewStub navStub = (WatchViewStub) mLayoutInflater.inflate(
+ final ActionPage navActionPage = (ActionPage) mLayoutInflater.inflate(
R.layout.gridpager_action, container, false);
- navStub.setOnClickListener(getStartActionClickListener(
+ navActionPage.setOnClickListener(getStartActionClickListener(
attraction, Constants.START_NAVIGATION_PATH,
ConfirmationActivity.SUCCESS_ANIMATION));
+ navActionPage.setImageResource(R.drawable.ic_full_directions_walking);
+ navActionPage.setText(mContext.getString(R.string.action_navigate));
- navStub.setOnLayoutInflatedListener(
- new WatchViewStub.OnLayoutInflatedListener() {
- @Override
- public void onLayoutInflated(WatchViewStub watchViewStub) {
- ImageView imageView = (ImageView) navStub.findViewById(R.id.imageView);
- imageView.setImageResource(R.drawable.ic_full_directions_walking);
- TextView textView = (TextView) navStub.findViewById(R.id.textView);
- textView.setText(R.string.action_navigate);
- }
- });
-
- container.addView(navStub);
- return navStub;
+ container.addView(navActionPage);
+ return navActionPage;
case PAGER_OPEN_ACTION_COLUMN:
// The "open on device" action
- final WatchViewStub openStub = (WatchViewStub) mLayoutInflater.inflate(
+ final ActionPage openActionPage = (ActionPage) mLayoutInflater.inflate(
R.layout.gridpager_action, container, false);
- openStub.setOnClickListener(getStartActionClickListener(
+ openActionPage.setOnClickListener(getStartActionClickListener(
attraction, Constants.START_ATTRACTION_PATH,
ConfirmationActivity.OPEN_ON_PHONE_ANIMATION));
+ openActionPage.setImageResource(R.drawable.ic_full_openonphone);
+ openActionPage.setText(mContext.getString(R.string.action_open));
- openStub.setOnLayoutInflatedListener(
- new WatchViewStub.OnLayoutInflatedListener() {
- @Override
- public void onLayoutInflated(WatchViewStub watchViewStub) {
- ImageView imageView = (ImageView) openStub.findViewById(R.id.imageView);
- imageView.setImageResource(R.drawable.ic_full_open_on_device);
- TextView textView = (TextView) openStub.findViewById(R.id.textView);
- textView.setText(R.string.action_open);
- }
- });
-
- container.addView(openStub);
- return openStub;
+ container.addView(openActionPage);
+ return openActionPage;
}
}
return new View(mContext);
@@ -209,7 +191,7 @@
}
@Override
- protected void destroyItem(ViewGroup viewGroup, int row, int column, Object object) {
+ public void destroyItem(ViewGroup viewGroup, int row, int column, Object object) {
mDelayedHide.remove((View) object);
viewGroup.removeView((View)object);
}
@@ -242,6 +224,7 @@
private void startAction(Attraction attraction, String pathName, int confirmAnimationType) {
Intent intent = new Intent(mContext, ConfirmationActivity.class);
intent.putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE, confirmAnimationType);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
mContext.startActivity(intent);
UtilityService.clearNotification(mContext);
@@ -270,8 +253,8 @@
}
public interface OnChromeFadeListener {
- abstract void onChromeFadeIn();
- abstract void onChromeFadeOut();
+ void onChromeFadeIn();
+ void onChromeFadeOut();
}
/**
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_full_open_on_device.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_full_open_on_device.png
deleted file mode 100644
index 4e11601..0000000
--- a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_full_open_on_device.png
+++ /dev/null
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_full_openonphone.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_full_openonphone.png
new file mode 100644
index 0000000..8952486
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_full_openonphone.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_open_on_device.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_open_on_device.png
deleted file mode 100644
index 2f6f056..0000000
--- a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_open_on_device.png
+++ /dev/null
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_openonphone.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_openonphone.png
new file mode 100644
index 0000000..d10a19c
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_openonphone.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/activity_main.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/activity_main.xml
index a4ef94b..27fce1f 100644
--- a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/activity_main.xml
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/activity_main.xml
@@ -41,9 +41,6 @@
android:layout_height="wrap_content"
android:layout_width="wrap_content"
app:dotFadeWhenIdle="false"
- app:dotFadeInDuration="0"
- app:dotFadeOutDuration="0"
- app:dotFadeOutDelay="0"
android:visibility="gone" />
<android.support.wearable.view.DismissOverlayView
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action.xml
index 4b3bbaf..ac01509 100644
--- a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action.xml
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action.xml
@@ -16,11 +16,14 @@
limitations under the License.
-->
-<android.support.wearable.view.WatchViewStub
+<android.support.wearable.view.ActionPage
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/actionpage"
android:layout_width="match_parent"
android:layout_height="match_parent"
- app:rectLayout="@layout/gridpager_action_square"
- app:roundLayout="@layout/gridpager_action_round"
- android:clickable="true" />
+ android:src="@drawable/ic_full_openonphone"
+ android:text="@string/action_open"
+ android:maxLines="1"
+ android:color="@color/colorPrimary"
+ app:rippleColor="@color/colorAccent" />
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action_round.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action_round.xml
deleted file mode 100644
index 70cec1a..0000000
--- a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action_round.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- Copyright 2015 Google Inc. All rights reserved.
-
- 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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <View
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#7F000000"/>
-
- <android.support.wearable.view.CircledImageView
- android:id="@+id/circleImageView"
- android:layout_width="112dp"
- android:layout_height="112dp"
- android:layout_centerInParent="true"
- app:circle_radius="52dp"
- app:circle_radius_pressed="56dp"
- app:circle_color="@color/action_color">
-
- <ImageView
- android:id="@+id/imageView"
- android:layout_width="64dp"
- android:layout_height="64dp"
- android:layout_gravity="center"
- android:src="@drawable/ic_full_open_on_device"
- android:scaleType="centerCrop" />
-
- </android.support.wearable.view.CircledImageView>
-
- <TextView
- android:id="@+id/textView"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="@style/ActionTextStyleRound"
- android:layout_below="@id/circleImageView"
- android:layout_centerHorizontal="true"
- android:gravity="center"
- tools:text="Navigate" />
-
-</RelativeLayout>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action_square.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action_square.xml
deleted file mode 100644
index 362671b..0000000
--- a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action_square.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- Copyright 2015 Google Inc. All rights reserved.
-
- 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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <View
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#7F000000"/>
-
- <android.support.wearable.view.CircledImageView
- android:id="@+id/circleImageView"
- android:layout_width="112dp"
- android:layout_height="112dp"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="24dp"
- app:circle_radius="52dp"
- app:circle_radius_pressed="56dp"
- app:circle_color="@color/action_color">
-
- <ImageView
- android:id="@+id/imageView"
- android:layout_width="64dp"
- android:layout_height="64dp"
- android:layout_gravity="center"
- android:src="@drawable/ic_full_open_on_device"
- android:scaleType="centerCrop" />
-
- </android.support.wearable.view.CircledImageView>
-
- <TextView
- android:id="@+id/textView"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- style="@style/ActionTextStyle"
- android:layout_below="@id/circleImageView"
- android:layout_marginBottom="12dp"
- android:layout_centerHorizontal="true"
- android:maxLines="2"
- android:gravity="center"
- tools:text="Navigate" />
-
-</RelativeLayout>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/values/styles.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/values/styles.xml
index cebe1c7..11b2d3d 100644
--- a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/values/styles.xml
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/values/styles.xml
@@ -31,18 +31,4 @@
<item name="android:ellipsize">end</item>
</style>
- <style name="ActionTextStyle" parent="@android:style/TextAppearance.Large">
- <item name="android:fontFamily">sans-serif-condensed-light</item>
- <item name="android:textStyle">normal</item>
- <item name="android:textSize">18sp</item>
- <item name="android:maxLines">2</item>
- <item name="android:ellipsize">end</item>
- <item name="android:textColor">#FFFFFF</item>
- </style>
-
- <style name="ActionTextStyleRound" parent="ActionTextStyle">
- <item name="android:textSize">14sp</item>
- <item name="android:maxLines">1</item>
- </style>
-
</resources>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/template-params.xml b/wearable/wear/XYZTouristAttractions/template-params.xml
index 0b64557..51882ac 100644
--- a/wearable/wear/XYZTouristAttractions/template-params.xml
+++ b/wearable/wear/XYZTouristAttractions/template-params.xml
@@ -24,11 +24,11 @@
<has_handheld_app>true</has_handheld_app>
</wearable>
- <dependency>com.android.support:appcompat-v7:21.0.3</dependency>
+ <dependency>com.android.support:appcompat-v7:22.1.1</dependency>
<dependency>com.google.android.gms:play-services-location:7.3.0</dependency>
<dependency>com.google.maps.android:android-maps-utils:0.3.2</dependency>
<dependency>com.github.bumptech.glide:glide:3.5.2</dependency>
- <dependency>com.android.support:recyclerview-v7:21.0.0</dependency>
+ <dependency>com.android.support:recyclerview-v7:22.1.1</dependency>
<dependency_wearable>com.google.android.gms:play-services-location:7.3.0</dependency_wearable>
<dependency_shared>com.google.android.gms:play-services-wearable:7.3.0</dependency_shared>
<dependency_shared>com.google.android.gms:play-services-location:7.3.0</dependency_shared>
@@ -96,6 +96,7 @@
<android>android.support.wearable.view.CardScrollView</android>
<android>android.support.wearable.view.GridPagerAdapter</android>
<android>android.support.wearable.view.WatchViewStub</android>
+ <android>android.support.wearable.view.ActionPage</android>
</api_refs>
<!-- 1-3 line description of the sample here.