diff --git a/samples/browseable/ActiveNotifications/AndroidManifest.xml b/samples/browseable/ActiveNotifications/AndroidManifest.xml
new file mode 100644
index 000000000..3a3928290
--- /dev/null
+++ b/samples/browseable/ActiveNotifications/AndroidManifest.xml
@@ -0,0 +1,37 @@
+
+
+
+ +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. + +
diff --git a/samples/browseable/ActiveNotifications/res/drawable-hdpi/tile.9.png b/samples/browseable/ActiveNotifications/res/drawable-hdpi/tile.9.png new file mode 100644 index 000000000..135862883 Binary files /dev/null and b/samples/browseable/ActiveNotifications/res/drawable-hdpi/tile.9.png differ diff --git a/samples/browseable/ActiveNotifications/res/layout-w720dp/activity_main.xml b/samples/browseable/ActiveNotifications/res/layout-w720dp/activity_main.xml new file mode 100755 index 000000000..c9a52f621 --- /dev/null +++ b/samples/browseable/ActiveNotifications/res/layout-w720dp/activity_main.xml @@ -0,0 +1,73 @@ + ++ * For devices with displays with a width of 720dp or greater, the sample log is always visible, + * on other devices it's visibility is controlled by an item on the Action Bar. + */ +public class MainActivity extends SampleActivityBase { + + public static final String TAG = "MainActivity"; + + // Whether the Log Fragment is currently shown + private boolean mLogShown; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + if (savedInstanceState == null) { + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + ActiveNotificationFragment fragment = new ActiveNotificationFragment(); + transaction.replace(R.id.sample_content_fragment, fragment); + transaction.commit(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem logToggle = menu.findItem(R.id.menu_toggle_log); + logToggle.setVisible(findViewById(R.id.sample_output) instanceof ViewAnimator); + logToggle.setTitle(mLogShown ? R.string.sample_hide_log : R.string.sample_show_log); + + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch(item.getItemId()) { + case R.id.menu_toggle_log: + mLogShown = !mLogShown; + ViewAnimator output = (ViewAnimator) findViewById(R.id.sample_output); + if (mLogShown) { + output.setDisplayedChild(1); + } else { + output.setDisplayedChild(0); + } + supportInvalidateOptionsMenu(); + return true; + } + return super.onOptionsItemSelected(item); + } + + /** Create a chain of targets that will receive log data */ + @Override + public void initializeLogging() { + // Wraps Android's native log framework. + LogWrapper logWrapper = new LogWrapper(); + // Using Log, front-end to the logging chain, emulates android.util.log method signatures. + Log.setLogNode(logWrapper); + + // Filter strips out everything except the message text. + MessageOnlyLogFilter msgFilter = new MessageOnlyLogFilter(); + logWrapper.setNext(msgFilter); + + // On screen logging via a fragment with a TextView. + LogFragment logFragment = (LogFragment) getSupportFragmentManager() + .findFragmentById(R.id.log_fragment); + msgFilter.setNext(logFragment.getLogView()); + + Log.i(TAG, "Ready"); + } +} diff --git a/samples/browseable/ActiveNotifications/src/com.example.android.common/activities/SampleActivityBase.java b/samples/browseable/ActiveNotifications/src/com.example.android.common/activities/SampleActivityBase.java new file mode 100644 index 000000000..3228927b7 --- /dev/null +++ b/samples/browseable/ActiveNotifications/src/com.example.android.common/activities/SampleActivityBase.java @@ -0,0 +1,52 @@ +/* +* Copyright 2013 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.example.android.common.activities; + +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; + +import com.example.android.common.logger.Log; +import com.example.android.common.logger.LogWrapper; + +/** + * Base launcher activity, to handle most of the common plumbing for samples. + */ +public class SampleActivityBase extends FragmentActivity { + + public static final String TAG = "SampleActivityBase"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + protected void onStart() { + super.onStart(); + initializeLogging(); + } + + /** Set up targets to receive log data */ + public void initializeLogging() { + // Using Log, front-end to the logging chain, emulates android.util.log method signatures. + // Wraps Android's native log framework + LogWrapper logWrapper = new LogWrapper(); + Log.setLogNode(logWrapper); + + Log.i(TAG, "Ready"); + } +} diff --git a/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/Log.java b/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/Log.java new file mode 100644 index 000000000..17503c568 --- /dev/null +++ b/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/Log.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Helper class for a list (or tree) of LoggerNodes. + * + *
When this is set as the head of the list, + * an instance of it can function as a drop-in replacement for {@link android.util.Log}. + * Most of the methods in this class server only to map a method call in Log to its equivalent + * in LogNode.
+ */ +public class Log { + // Grabbing the native values from Android's native logging facilities, + // to make for easy migration and interop. + public static final int NONE = -1; + public static final int VERBOSE = android.util.Log.VERBOSE; + public static final int DEBUG = android.util.Log.DEBUG; + public static final int INFO = android.util.Log.INFO; + public static final int WARN = android.util.Log.WARN; + public static final int ERROR = android.util.Log.ERROR; + public static final int ASSERT = android.util.Log.ASSERT; + + // Stores the beginning of the LogNode topology. + private static LogNode mLogNode; + + /** + * Returns the next LogNode in the linked list. + */ + public static LogNode getLogNode() { + return mLogNode; + } + + /** + * Sets the LogNode data will be sent to. + */ + public static void setLogNode(LogNode node) { + mLogNode = node; + } + + /** + * Instructs the LogNode to print the log data provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void println(int priority, String tag, String msg, Throwable tr) { + if (mLogNode != null) { + mLogNode.println(priority, tag, msg, tr); + } + } + + /** + * Instructs the LogNode to print the log data provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + */ + public static void println(int priority, String tag, String msg) { + println(priority, tag, msg, null); + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void v(String tag, String msg, Throwable tr) { + println(VERBOSE, tag, msg, tr); + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void v(String tag, String msg) { + v(tag, msg, null); + } + + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void d(String tag, String msg, Throwable tr) { + println(DEBUG, tag, msg, tr); + } + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void d(String tag, String msg) { + d(tag, msg, null); + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void i(String tag, String msg, Throwable tr) { + println(INFO, tag, msg, tr); + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void i(String tag, String msg) { + i(tag, msg, null); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void w(String tag, String msg, Throwable tr) { + println(WARN, tag, msg, tr); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void w(String tag, String msg) { + w(tag, msg, null); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void w(String tag, Throwable tr) { + w(tag, null, tr); + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void e(String tag, String msg, Throwable tr) { + println(ERROR, tag, msg, tr); + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void e(String tag, String msg) { + e(tag, msg, null); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void wtf(String tag, String msg, Throwable tr) { + println(ASSERT, tag, msg, tr); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void wtf(String tag, String msg) { + wtf(tag, msg, null); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void wtf(String tag, Throwable tr) { + wtf(tag, null, tr); + } +} diff --git a/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/LogFragment.java b/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/LogFragment.java new file mode 100644 index 000000000..b302acd4b --- /dev/null +++ b/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/LogFragment.java @@ -0,0 +1,109 @@ +/* +* Copyright 2013 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +/* + * Copyright 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.common.logger; + +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ScrollView; + +/** + * Simple fraggment which contains a LogView and uses is to output log data it receives + * through the LogNode interface. + */ +public class LogFragment extends Fragment { + + private LogView mLogView; + private ScrollView mScrollView; + + public LogFragment() {} + + public View inflateViews() { + mScrollView = new ScrollView(getActivity()); + ViewGroup.LayoutParams scrollParams = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + mScrollView.setLayoutParams(scrollParams); + + mLogView = new LogView(getActivity()); + ViewGroup.LayoutParams logParams = new ViewGroup.LayoutParams(scrollParams); + logParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; + mLogView.setLayoutParams(logParams); + mLogView.setClickable(true); + mLogView.setFocusable(true); + mLogView.setTypeface(Typeface.MONOSPACE); + + // Want to set padding as 16 dips, setPadding takes pixels. Hooray math! + int paddingDips = 16; + double scale = getResources().getDisplayMetrics().density; + int paddingPixels = (int) ((paddingDips * (scale)) + .5); + mLogView.setPadding(paddingPixels, paddingPixels, paddingPixels, paddingPixels); + mLogView.setCompoundDrawablePadding(paddingPixels); + + mLogView.setGravity(Gravity.BOTTOM); + mLogView.setTextAppearance(getActivity(), android.R.style.TextAppearance_Holo_Medium); + + mScrollView.addView(mLogView); + return mScrollView; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View result = inflateViews(); + + mLogView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + mScrollView.fullScroll(ScrollView.FOCUS_DOWN); + } + }); + return result; + } + + public LogView getLogView() { + return mLogView; + } +} \ No newline at end of file diff --git a/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/LogNode.java b/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/LogNode.java new file mode 100644 index 000000000..bc37cabc0 --- /dev/null +++ b/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/LogNode.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Basic interface for a logging system that can output to one or more targets. + * Note that in addition to classes that will output these logs in some format, + * one can also implement this interface over a filter and insert that in the chain, + * such that no targets further down see certain data, or see manipulated forms of the data. + * You could, for instance, write a "ToHtmlLoggerNode" that just converted all the log data + * it received to HTML and sent it along to the next node in the chain, without printing it + * anywhere. + */ +public interface LogNode { + + /** + * Instructs first LogNode in the list to print the log data provided. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public void println(int priority, String tag, String msg, Throwable tr); + +} diff --git a/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/LogView.java b/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/LogView.java new file mode 100644 index 000000000..c01542b91 --- /dev/null +++ b/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/LogView.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +import android.app.Activity; +import android.content.Context; +import android.util.*; +import android.widget.TextView; + +/** Simple TextView which is used to output log data received through the LogNode interface. +*/ +public class LogView extends TextView implements LogNode { + + public LogView(Context context) { + super(context); + } + + public LogView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LogView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Formats the log data and prints it out to the LogView. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + + + String priorityStr = null; + + // For the purposes of this View, we want to print the priority as readable text. + switch(priority) { + case android.util.Log.VERBOSE: + priorityStr = "VERBOSE"; + break; + case android.util.Log.DEBUG: + priorityStr = "DEBUG"; + break; + case android.util.Log.INFO: + priorityStr = "INFO"; + break; + case android.util.Log.WARN: + priorityStr = "WARN"; + break; + case android.util.Log.ERROR: + priorityStr = "ERROR"; + break; + case android.util.Log.ASSERT: + priorityStr = "ASSERT"; + break; + default: + break; + } + + // Handily, the Log class has a facility for converting a stack trace into a usable string. + String exceptionStr = null; + if (tr != null) { + exceptionStr = android.util.Log.getStackTraceString(tr); + } + + // Take the priority, tag, message, and exception, and concatenate as necessary + // into one usable line of text. + final StringBuilder outputBuilder = new StringBuilder(); + + String delimiter = "\t"; + appendIfNotNull(outputBuilder, priorityStr, delimiter); + appendIfNotNull(outputBuilder, tag, delimiter); + appendIfNotNull(outputBuilder, msg, delimiter); + appendIfNotNull(outputBuilder, exceptionStr, delimiter); + + // In case this was originally called from an AsyncTask or some other off-UI thread, + // make sure the update occurs within the UI thread. + ((Activity) getContext()).runOnUiThread( (new Thread(new Runnable() { + @Override + public void run() { + // Display the text we just generated within the LogView. + appendToLog(outputBuilder.toString()); + } + }))); + + if (mNext != null) { + mNext.println(priority, tag, msg, tr); + } + } + + public LogNode getNext() { + return mNext; + } + + public void setNext(LogNode node) { + mNext = node; + } + + /** Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since + * the logger takes so many arguments that might be null, this method helps cut out some of the + * agonizing tedium of writing the same 3 lines over and over. + * @param source StringBuilder containing the text to append to. + * @param addStr The String to append + * @param delimiter The String to separate the source and appended strings. A tab or comma, + * for instance. + * @return The fully concatenated String as a StringBuilder + */ + private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) { + if (addStr != null) { + if (addStr.length() == 0) { + delimiter = ""; + } + + return source.append(addStr).append(delimiter); + } + return source; + } + + // The next LogNode in the chain. + LogNode mNext; + + /** Outputs the string as a new line of log data in the LogView. */ + public void appendToLog(String s) { + append("\n" + s); + } + + +} diff --git a/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/LogWrapper.java b/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/LogWrapper.java new file mode 100644 index 000000000..16a9e7ba2 --- /dev/null +++ b/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/LogWrapper.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +import android.util.Log; + +/** + * Helper class which wraps Android's native Log utility in the Logger interface. This way + * normal DDMS output can be one of the many targets receiving and outputting logs simultaneously. + */ +public class LogWrapper implements LogNode { + + // For piping: The next node to receive Log data after this one has done its work. + private LogNode mNext; + + /** + * Returns the next LogNode in the linked list. + */ + public LogNode getNext() { + return mNext; + } + + /** + * Sets the LogNode data will be sent to.. + */ + public void setNext(LogNode node) { + mNext = node; + } + + /** + * Prints data out to the console using Android's native log mechanism. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + // There actually are log methods that don't take a msg parameter. For now, + // if that's the case, just convert null to the empty string and move on. + String useMsg = msg; + if (useMsg == null) { + useMsg = ""; + } + + // If an exeption was provided, convert that exception to a usable string and attach + // it to the end of the msg method. + if (tr != null) { + msg += "\n" + Log.getStackTraceString(tr); + } + + // This is functionally identical to Log.x(tag, useMsg); + // For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg) + Log.println(priority, tag, useMsg); + + // If this isn't the last node in the chain, move things along. + if (mNext != null) { + mNext.println(priority, tag, msg, tr); + } + } +} diff --git a/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/MessageOnlyLogFilter.java b/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/MessageOnlyLogFilter.java new file mode 100644 index 000000000..19967dcd4 --- /dev/null +++ b/samples/browseable/ActiveNotifications/src/com.example.android.common/logger/MessageOnlyLogFilter.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Simple {@link LogNode} filter, removes everything except the message. + * Useful for situations like on-screen log output where you don't want a lot of metadata displayed, + * just easy-to-read message updates as they're happening. + */ +public class MessageOnlyLogFilter implements LogNode { + + LogNode mNext; + + /** + * Takes the "next" LogNode as a parameter, to simplify chaining. + * + * @param next The next LogNode in the pipeline. + */ + public MessageOnlyLogFilter(LogNode next) { + mNext = next; + } + + public MessageOnlyLogFilter() { + } + + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + if (mNext != null) { + getNext().println(Log.NONE, null, msg, null); + } + } + + /** + * Returns the next LogNode in the chain. + */ + public LogNode getNext() { + return mNext; + } + + /** + * Sets the LogNode data will be sent to.. + */ + public void setNext(LogNode node) { + mNext = node; + } + +} diff --git a/samples/browseable/AlwaysOn/src/com.example.android.wearable.wear.alwayson/MainActivity.java b/samples/browseable/AlwaysOn/src/com.example.android.wearable.wear.alwayson/MainActivity.java index 0eb7f470d..3673545e0 100644 --- a/samples/browseable/AlwaysOn/src/com.example.android.wearable.wear.alwayson/MainActivity.java +++ b/samples/browseable/AlwaysOn/src/com.example.android.wearable.wear.alwayson/MainActivity.java @@ -264,7 +264,6 @@ public class MainActivity extends WearableActivity { * 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.cancel(mAmbientStatePendingIntent); mAmbientStateAlarmManager.setExact( AlarmManager.RTC_WAKEUP, triggerTimeMs, diff --git a/samples/browseable/AutoBackupForApps/AndroidManifest.xml b/samples/browseable/AutoBackupForApps/AndroidManifest.xml new file mode 100644 index 000000000..7dad1e006 --- /dev/null +++ b/samples/browseable/AutoBackupForApps/AndroidManifest.xml @@ -0,0 +1,37 @@ + + ++ +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. + +
diff --git a/samples/browseable/AutoBackupForApps/res/drawable-hdpi/tile.9.png b/samples/browseable/AutoBackupForApps/res/drawable-hdpi/tile.9.png new file mode 100644 index 000000000..135862883 Binary files /dev/null and b/samples/browseable/AutoBackupForApps/res/drawable-hdpi/tile.9.png differ diff --git a/samples/browseable/AutoBackupForApps/res/layout/activity_main.xml b/samples/browseable/AutoBackupForApps/res/layout/activity_main.xml new file mode 100644 index 000000000..4a7879dca --- /dev/null +++ b/samples/browseable/AutoBackupForApps/res/layout/activity_main.xml @@ -0,0 +1,20 @@ + + ++ + This sample demonstrates how to use the Camera2 API to capture RAW + camera buffers and save them as DNG files. + +
diff --git a/samples/browseable/Camera2Raw/res/drawable-hdpi/ic_action_info.png b/samples/browseable/Camera2Raw/res/drawable-hdpi/ic_action_info.png new file mode 100644 index 000000000..32bd1aabc Binary files /dev/null and b/samples/browseable/Camera2Raw/res/drawable-hdpi/ic_action_info.png differ diff --git a/samples/browseable/Camera2Raw/res/drawable-hdpi/ic_launcher.png b/samples/browseable/Camera2Raw/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..bba1165cf Binary files /dev/null and b/samples/browseable/Camera2Raw/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/browseable/Camera2Raw/res/drawable-hdpi/tile.9.png b/samples/browseable/Camera2Raw/res/drawable-hdpi/tile.9.png new file mode 100644 index 000000000..135862883 Binary files /dev/null and b/samples/browseable/Camera2Raw/res/drawable-hdpi/tile.9.png differ diff --git a/samples/browseable/Camera2Raw/res/drawable-mdpi/ic_action_info.png b/samples/browseable/Camera2Raw/res/drawable-mdpi/ic_action_info.png new file mode 100644 index 000000000..8efbbf8b3 Binary files /dev/null and b/samples/browseable/Camera2Raw/res/drawable-mdpi/ic_action_info.png differ diff --git a/samples/browseable/Camera2Raw/res/drawable-mdpi/ic_launcher.png b/samples/browseable/Camera2Raw/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..430459100 Binary files /dev/null and b/samples/browseable/Camera2Raw/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/browseable/Camera2Raw/res/drawable-xhdpi/ic_action_info.png b/samples/browseable/Camera2Raw/res/drawable-xhdpi/ic_action_info.png new file mode 100644 index 000000000..ba143ea7a Binary files /dev/null and b/samples/browseable/Camera2Raw/res/drawable-xhdpi/ic_action_info.png differ diff --git a/samples/browseable/Camera2Raw/res/drawable-xhdpi/ic_launcher.png b/samples/browseable/Camera2Raw/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..80c5ebaaf Binary files /dev/null and b/samples/browseable/Camera2Raw/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/browseable/Camera2Raw/res/drawable-xxhdpi/ic_action_info.png b/samples/browseable/Camera2Raw/res/drawable-xxhdpi/ic_action_info.png new file mode 100644 index 000000000..394eb7e53 Binary files /dev/null and b/samples/browseable/Camera2Raw/res/drawable-xxhdpi/ic_action_info.png differ diff --git a/samples/browseable/Camera2Raw/res/drawable-xxhdpi/ic_launcher.png b/samples/browseable/Camera2Raw/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..9baac9b6f Binary files /dev/null and b/samples/browseable/Camera2Raw/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/samples/browseable/Camera2Raw/res/layout-land/fragment_camera2_basic.xml b/samples/browseable/Camera2Raw/res/layout-land/fragment_camera2_basic.xml new file mode 100644 index 000000000..3eb68db2e --- /dev/null +++ b/samples/browseable/Camera2Raw/res/layout-land/fragment_camera2_basic.xml @@ -0,0 +1,59 @@ + ++ +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. + +
diff --git a/samples/browseable/ConfirmCredential/res/drawable-hdpi/tile.9.png b/samples/browseable/ConfirmCredential/res/drawable-hdpi/tile.9.png new file mode 100644 index 000000000..135862883 Binary files /dev/null and b/samples/browseable/ConfirmCredential/res/drawable-hdpi/tile.9.png differ diff --git a/samples/browseable/ConfirmCredential/res/drawable-nodpi/android_robot.png b/samples/browseable/ConfirmCredential/res/drawable-nodpi/android_robot.png new file mode 100644 index 000000000..40bf934bb Binary files /dev/null and b/samples/browseable/ConfirmCredential/res/drawable-nodpi/android_robot.png differ diff --git a/samples/browseable/ConfirmCredential/res/drawable/card.xml b/samples/browseable/ConfirmCredential/res/drawable/card.xml new file mode 100644 index 000000000..691a4c556 --- /dev/null +++ b/samples/browseable/ConfirmCredential/res/drawable/card.xml @@ -0,0 +1,24 @@ + + ++ +This sample demonstrates how you can use registered fingerprints to authenticate the user +before proceeding some actions such as purchasing an item. + +
diff --git a/samples/browseable/FingerprintDialog/res/drawable-hdpi/ic_fp_40px.png b/samples/browseable/FingerprintDialog/res/drawable-hdpi/ic_fp_40px.png new file mode 100644 index 000000000..48ebd8ad7 Binary files /dev/null and b/samples/browseable/FingerprintDialog/res/drawable-hdpi/ic_fp_40px.png differ diff --git a/samples/browseable/FingerprintDialog/res/drawable-hdpi/tile.9.png b/samples/browseable/FingerprintDialog/res/drawable-hdpi/tile.9.png new file mode 100644 index 000000000..135862883 Binary files /dev/null and b/samples/browseable/FingerprintDialog/res/drawable-hdpi/tile.9.png differ diff --git a/samples/browseable/FingerprintDialog/res/drawable-mdpi/ic_fp_40px.png b/samples/browseable/FingerprintDialog/res/drawable-mdpi/ic_fp_40px.png new file mode 100644 index 000000000..122f44257 Binary files /dev/null and b/samples/browseable/FingerprintDialog/res/drawable-mdpi/ic_fp_40px.png differ diff --git a/samples/browseable/FingerprintDialog/res/drawable-nodpi/android_robot.png b/samples/browseable/FingerprintDialog/res/drawable-nodpi/android_robot.png new file mode 100644 index 000000000..40bf934bb Binary files /dev/null and b/samples/browseable/FingerprintDialog/res/drawable-nodpi/android_robot.png differ diff --git a/samples/browseable/FingerprintDialog/res/drawable-xhdpi/ic_fp_40px.png b/samples/browseable/FingerprintDialog/res/drawable-xhdpi/ic_fp_40px.png new file mode 100644 index 000000000..e1c9590bb Binary files /dev/null and b/samples/browseable/FingerprintDialog/res/drawable-xhdpi/ic_fp_40px.png differ diff --git a/samples/browseable/FingerprintDialog/res/drawable-xxhdpi/ic_fp_40px.png b/samples/browseable/FingerprintDialog/res/drawable-xxhdpi/ic_fp_40px.png new file mode 100644 index 000000000..f7e87240e Binary files /dev/null and b/samples/browseable/FingerprintDialog/res/drawable-xxhdpi/ic_fp_40px.png differ diff --git a/samples/browseable/FingerprintDialog/res/drawable-xxxhdpi/ic_fp_40px.png b/samples/browseable/FingerprintDialog/res/drawable-xxxhdpi/ic_fp_40px.png new file mode 100644 index 000000000..0fb854524 Binary files /dev/null and b/samples/browseable/FingerprintDialog/res/drawable-xxxhdpi/ic_fp_40px.png differ diff --git a/samples/browseable/FingerprintDialog/res/drawable/card.xml b/samples/browseable/FingerprintDialog/res/drawable/card.xml new file mode 100644 index 000000000..691a4c556 --- /dev/null +++ b/samples/browseable/FingerprintDialog/res/drawable/card.xml @@ -0,0 +1,24 @@ + + ++ + 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. + +
diff --git a/samples/browseable/RuntimePermissions/res/drawable-hdpi/tile.9.png b/samples/browseable/RuntimePermissions/res/drawable-hdpi/tile.9.png new file mode 100644 index 000000000..135862883 Binary files /dev/null and b/samples/browseable/RuntimePermissions/res/drawable-hdpi/tile.9.png differ diff --git a/samples/browseable/RuntimePermissions/res/layout-w720dp/activity_main.xml b/samples/browseable/RuntimePermissions/res/layout-w720dp/activity_main.xml new file mode 100644 index 000000000..c9a52f621 --- /dev/null +++ b/samples/browseable/RuntimePermissions/res/layout-w720dp/activity_main.xml @@ -0,0 +1,73 @@ + +When this is set as the head of the list, + * an instance of it can function as a drop-in replacement for {@link android.util.Log}. + * Most of the methods in this class server only to map a method call in Log to its equivalent + * in LogNode.
+ */ +public class Log { + // Grabbing the native values from Android's native logging facilities, + // to make for easy migration and interop. + public static final int NONE = -1; + public static final int VERBOSE = android.util.Log.VERBOSE; + public static final int DEBUG = android.util.Log.DEBUG; + public static final int INFO = android.util.Log.INFO; + public static final int WARN = android.util.Log.WARN; + public static final int ERROR = android.util.Log.ERROR; + public static final int ASSERT = android.util.Log.ASSERT; + + // Stores the beginning of the LogNode topology. + private static LogNode mLogNode; + + /** + * Returns the next LogNode in the linked list. + */ + public static LogNode getLogNode() { + return mLogNode; + } + + /** + * Sets the LogNode data will be sent to. + */ + public static void setLogNode(LogNode node) { + mLogNode = node; + } + + /** + * Instructs the LogNode to print the log data provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void println(int priority, String tag, String msg, Throwable tr) { + if (mLogNode != null) { + mLogNode.println(priority, tag, msg, tr); + } + } + + /** + * Instructs the LogNode to print the log data provided. Other LogNodes can + * be chained to the end of the LogNode as desired. + * + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + */ + public static void println(int priority, String tag, String msg) { + println(priority, tag, msg, null); + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void v(String tag, String msg, Throwable tr) { + println(VERBOSE, tag, msg, tr); + } + + /** + * Prints a message at VERBOSE priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void v(String tag, String msg) { + v(tag, msg, null); + } + + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void d(String tag, String msg, Throwable tr) { + println(DEBUG, tag, msg, tr); + } + + /** + * Prints a message at DEBUG priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void d(String tag, String msg) { + d(tag, msg, null); + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void i(String tag, String msg, Throwable tr) { + println(INFO, tag, msg, tr); + } + + /** + * Prints a message at INFO priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void i(String tag, String msg) { + i(tag, msg, null); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void w(String tag, String msg, Throwable tr) { + println(WARN, tag, msg, tr); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void w(String tag, String msg) { + w(tag, msg, null); + } + + /** + * Prints a message at WARN priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void w(String tag, Throwable tr) { + w(tag, null, tr); + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void e(String tag, String msg, Throwable tr) { + println(ERROR, tag, msg, tr); + } + + /** + * Prints a message at ERROR priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void e(String tag, String msg) { + e(tag, msg, null); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void wtf(String tag, String msg, Throwable tr) { + println(ASSERT, tag, msg, tr); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. + */ + public static void wtf(String tag, String msg) { + wtf(tag, msg, null); + } + + /** + * Prints a message at ASSERT priority. + * + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public static void wtf(String tag, Throwable tr) { + wtf(tag, null, tr); + } +} diff --git a/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/LogFragment.java b/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/LogFragment.java new file mode 100644 index 000000000..b302acd4b --- /dev/null +++ b/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/LogFragment.java @@ -0,0 +1,109 @@ +/* +* Copyright 2013 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +/* + * Copyright 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.common.logger; + +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ScrollView; + +/** + * Simple fraggment which contains a LogView and uses is to output log data it receives + * through the LogNode interface. + */ +public class LogFragment extends Fragment { + + private LogView mLogView; + private ScrollView mScrollView; + + public LogFragment() {} + + public View inflateViews() { + mScrollView = new ScrollView(getActivity()); + ViewGroup.LayoutParams scrollParams = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + mScrollView.setLayoutParams(scrollParams); + + mLogView = new LogView(getActivity()); + ViewGroup.LayoutParams logParams = new ViewGroup.LayoutParams(scrollParams); + logParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; + mLogView.setLayoutParams(logParams); + mLogView.setClickable(true); + mLogView.setFocusable(true); + mLogView.setTypeface(Typeface.MONOSPACE); + + // Want to set padding as 16 dips, setPadding takes pixels. Hooray math! + int paddingDips = 16; + double scale = getResources().getDisplayMetrics().density; + int paddingPixels = (int) ((paddingDips * (scale)) + .5); + mLogView.setPadding(paddingPixels, paddingPixels, paddingPixels, paddingPixels); + mLogView.setCompoundDrawablePadding(paddingPixels); + + mLogView.setGravity(Gravity.BOTTOM); + mLogView.setTextAppearance(getActivity(), android.R.style.TextAppearance_Holo_Medium); + + mScrollView.addView(mLogView); + return mScrollView; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View result = inflateViews(); + + mLogView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + mScrollView.fullScroll(ScrollView.FOCUS_DOWN); + } + }); + return result; + } + + public LogView getLogView() { + return mLogView; + } +} \ No newline at end of file diff --git a/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/LogNode.java b/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/LogNode.java new file mode 100644 index 000000000..bc37cabc0 --- /dev/null +++ b/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/LogNode.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Basic interface for a logging system that can output to one or more targets. + * Note that in addition to classes that will output these logs in some format, + * one can also implement this interface over a filter and insert that in the chain, + * such that no targets further down see certain data, or see manipulated forms of the data. + * You could, for instance, write a "ToHtmlLoggerNode" that just converted all the log data + * it received to HTML and sent it along to the next node in the chain, without printing it + * anywhere. + */ +public interface LogNode { + + /** + * Instructs first LogNode in the list to print the log data provided. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + public void println(int priority, String tag, String msg, Throwable tr); + +} diff --git a/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/LogView.java b/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/LogView.java new file mode 100644 index 000000000..c01542b91 --- /dev/null +++ b/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/LogView.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +import android.app.Activity; +import android.content.Context; +import android.util.*; +import android.widget.TextView; + +/** Simple TextView which is used to output log data received through the LogNode interface. +*/ +public class LogView extends TextView implements LogNode { + + public LogView(Context context) { + super(context); + } + + public LogView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LogView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Formats the log data and prints it out to the LogView. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + + + String priorityStr = null; + + // For the purposes of this View, we want to print the priority as readable text. + switch(priority) { + case android.util.Log.VERBOSE: + priorityStr = "VERBOSE"; + break; + case android.util.Log.DEBUG: + priorityStr = "DEBUG"; + break; + case android.util.Log.INFO: + priorityStr = "INFO"; + break; + case android.util.Log.WARN: + priorityStr = "WARN"; + break; + case android.util.Log.ERROR: + priorityStr = "ERROR"; + break; + case android.util.Log.ASSERT: + priorityStr = "ASSERT"; + break; + default: + break; + } + + // Handily, the Log class has a facility for converting a stack trace into a usable string. + String exceptionStr = null; + if (tr != null) { + exceptionStr = android.util.Log.getStackTraceString(tr); + } + + // Take the priority, tag, message, and exception, and concatenate as necessary + // into one usable line of text. + final StringBuilder outputBuilder = new StringBuilder(); + + String delimiter = "\t"; + appendIfNotNull(outputBuilder, priorityStr, delimiter); + appendIfNotNull(outputBuilder, tag, delimiter); + appendIfNotNull(outputBuilder, msg, delimiter); + appendIfNotNull(outputBuilder, exceptionStr, delimiter); + + // In case this was originally called from an AsyncTask or some other off-UI thread, + // make sure the update occurs within the UI thread. + ((Activity) getContext()).runOnUiThread( (new Thread(new Runnable() { + @Override + public void run() { + // Display the text we just generated within the LogView. + appendToLog(outputBuilder.toString()); + } + }))); + + if (mNext != null) { + mNext.println(priority, tag, msg, tr); + } + } + + public LogNode getNext() { + return mNext; + } + + public void setNext(LogNode node) { + mNext = node; + } + + /** Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since + * the logger takes so many arguments that might be null, this method helps cut out some of the + * agonizing tedium of writing the same 3 lines over and over. + * @param source StringBuilder containing the text to append to. + * @param addStr The String to append + * @param delimiter The String to separate the source and appended strings. A tab or comma, + * for instance. + * @return The fully concatenated String as a StringBuilder + */ + private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) { + if (addStr != null) { + if (addStr.length() == 0) { + delimiter = ""; + } + + return source.append(addStr).append(delimiter); + } + return source; + } + + // The next LogNode in the chain. + LogNode mNext; + + /** Outputs the string as a new line of log data in the LogView. */ + public void appendToLog(String s) { + append("\n" + s); + } + + +} diff --git a/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/LogWrapper.java b/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/LogWrapper.java new file mode 100644 index 000000000..16a9e7ba2 --- /dev/null +++ b/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/LogWrapper.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +import android.util.Log; + +/** + * Helper class which wraps Android's native Log utility in the Logger interface. This way + * normal DDMS output can be one of the many targets receiving and outputting logs simultaneously. + */ +public class LogWrapper implements LogNode { + + // For piping: The next node to receive Log data after this one has done its work. + private LogNode mNext; + + /** + * Returns the next LogNode in the linked list. + */ + public LogNode getNext() { + return mNext; + } + + /** + * Sets the LogNode data will be sent to.. + */ + public void setNext(LogNode node) { + mNext = node; + } + + /** + * Prints data out to the console using Android's native log mechanism. + * @param priority Log level of the data being logged. Verbose, Error, etc. + * @param tag Tag for for the log data. Can be used to organize log statements. + * @param msg The actual message to be logged. The actual message to be logged. + * @param tr If an exception was thrown, this can be sent along for the logging facilities + * to extract and print useful information. + */ + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + // There actually are log methods that don't take a msg parameter. For now, + // if that's the case, just convert null to the empty string and move on. + String useMsg = msg; + if (useMsg == null) { + useMsg = ""; + } + + // If an exeption was provided, convert that exception to a usable string and attach + // it to the end of the msg method. + if (tr != null) { + msg += "\n" + Log.getStackTraceString(tr); + } + + // This is functionally identical to Log.x(tag, useMsg); + // For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg) + Log.println(priority, tag, useMsg); + + // If this isn't the last node in the chain, move things along. + if (mNext != null) { + mNext.println(priority, tag, msg, tr); + } + } +} diff --git a/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/MessageOnlyLogFilter.java b/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/MessageOnlyLogFilter.java new file mode 100644 index 000000000..19967dcd4 --- /dev/null +++ b/samples/browseable/RuntimePermissions/src/com.example.android.common/logger/MessageOnlyLogFilter.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.common.logger; + +/** + * Simple {@link LogNode} filter, removes everything except the message. + * Useful for situations like on-screen log output where you don't want a lot of metadata displayed, + * just easy-to-read message updates as they're happening. + */ +public class MessageOnlyLogFilter implements LogNode { + + LogNode mNext; + + /** + * Takes the "next" LogNode as a parameter, to simplify chaining. + * + * @param next The next LogNode in the pipeline. + */ + public MessageOnlyLogFilter(LogNode next) { + mNext = next; + } + + public MessageOnlyLogFilter() { + } + + @Override + public void println(int priority, String tag, String msg, Throwable tr) { + if (mNext != null) { + getNext().println(Log.NONE, null, msg, null); + } + } + + /** + * Returns the next LogNode in the chain. + */ + public LogNode getNext() { + return mNext; + } + + /** + * Sets the LogNode data will be sent to.. + */ + public void setNext(LogNode node) { + mNext = node; + } + +} diff --git a/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/MainActivity.java b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/MainActivity.java new file mode 100644 index 000000000..43436aa5f --- /dev/null +++ b/samples/browseable/RuntimePermissions/src/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. + *+ * 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[])}. + *
+ * 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. + *
+ * 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. + *
+ * (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/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/PermissionUtil.java b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/PermissionUtil.java new file mode 100644 index 000000000..ba2370fda --- /dev/null +++ b/samples/browseable/RuntimePermissions/src/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/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/RuntimePermissionsFragment.java b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/RuntimePermissionsFragment.java new file mode 100644 index 000000000..b35bfebc0 --- /dev/null +++ b/samples/browseable/RuntimePermissions/src/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/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/camera/CameraPreview.java b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/camera/CameraPreview.java new file mode 100644 index 000000000..1d25b51de --- /dev/null +++ b/samples/browseable/RuntimePermissions/src/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. + *
+ * 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/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/camera/CameraPreviewFragment.java b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/camera/CameraPreviewFragment.java new file mode 100644 index 000000000..d0938f672 --- /dev/null +++ b/samples/browseable/RuntimePermissions/src/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. + *
+ * 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. + *
+ * 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/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/contacts/ContactsFragment.java b/samples/browseable/RuntimePermissions/src/com.example.android.system.runtimepermissions/contacts/ContactsFragment.java new file mode 100644 index 000000000..19f54fb9a --- /dev/null +++ b/samples/browseable/RuntimePermissions/src/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. + *
+ * 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. + *
+ * 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
+ * The contact is called "__DUMMY ENTRY" and only contains a name.
+ */
+ private void insertDummyContact() {
+ // Two operations are needed to insert a new contact.
+ ArrayList
+
+ 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.
+
+
+ * 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/samples/browseable/RuntimePermissionsBasic/src/com.example.android.basicpermissions/camera/CameraPreview.java b/samples/browseable/RuntimePermissionsBasic/src/com.example.android.basicpermissions/camera/CameraPreview.java
new file mode 100644
index 000000000..7abf2d8da
--- /dev/null
+++ b/samples/browseable/RuntimePermissionsBasic/src/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.
+ *
+ * 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/samples/browseable/RuntimePermissionsBasic/src/com.example.android.basicpermissions/camera/CameraPreviewActivity.java b/samples/browseable/RuntimePermissionsBasic/src/com.example.android.basicpermissions/camera/CameraPreviewActivity.java
new file mode 100644
index 000000000..ee589d999
--- /dev/null
+++ b/samples/browseable/RuntimePermissionsBasic/src/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.
+ *
+ * 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.
+ *
+ * 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;
+ }
+ }
+}