merge in nyc-release history after reset to nyc-dev
diff --git a/samples/browseable/ActiveNotifications/AndroidManifest.xml b/samples/browseable/ActiveNotifications/AndroidManifest.xml
index 3a39282..022e211 100644
--- a/samples/browseable/ActiveNotifications/AndroidManifest.xml
+++ b/samples/browseable/ActiveNotifications/AndroidManifest.xml
@@ -24,7 +24,7 @@
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme">
- <activity android:name=".ActiveNotificationActivity"
+ <activity android:name=".ActiveNotificationsActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/samples/browseable/ActiveNotifications/_index.jd b/samples/browseable/ActiveNotifications/_index.jd
index d8ce4a9..7ba4b8a 100644
--- a/samples/browseable/ActiveNotifications/_index.jd
+++ b/samples/browseable/ActiveNotifications/_index.jd
@@ -1,13 +1,17 @@
-page.tags="ActiveNotification"
-sample.group=Notification
+page.tags="ActiveNotifications"
+sample.group=Android N Preview
@jd:body
<p>
-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.
+Notifications can now be grouped in Android N. Since Android M, the
+NotificationManager can tell you how many notifications your application
+is currently showing. This sample demonstrates how to use these APIs
+together for a nicer user experience when an app may have multiple
+notifications. To get started, press the "add a notification" button.
+If you add more than one notification a notification summary will be
+added. When a notification is being canceled, the count gets updated
+via a PendingIntent.
</p>
diff --git a/samples/browseable/ActiveNotifications/res/values/base-strings.xml b/samples/browseable/ActiveNotifications/res/values/base-strings.xml
index 5fc846c..1753fa9 100644
--- a/samples/browseable/ActiveNotifications/res/values/base-strings.xml
+++ b/samples/browseable/ActiveNotifications/res/values/base-strings.xml
@@ -16,15 +16,19 @@
-->
<resources>
- <string name="app_name">ActiveNotification</string>
+ <string name="app_name">ActiveNotifications</string>
<string name="intro_message">
<![CDATA[
-The NotificationManager can tell you how many notifications your application is currently showing.
-This sample demonstrates how to use this API that has been introduced with Android M.
-To get started, press the "add a notification" button.
-When a notification is being canceled, the count gets updated via a PendingIntent.
+Notifications can now be grouped in Android N. Since Android M, the
+NotificationManager can tell you how many notifications your application
+is currently showing. This sample demonstrates how to use these APIs
+together for a nicer user experience when an app may have multiple
+notifications. To get started, press the "add a notification" button.
+If you add more than one notification a notification summary will be
+added. When a notification is being canceled, the count gets updated
+via a PendingIntent.
]]>
diff --git a/samples/browseable/ActiveNotifications/res/values/strings.xml b/samples/browseable/ActiveNotifications/res/values/strings.xml
index 0f2977d..a4275fd 100644
--- a/samples/browseable/ActiveNotifications/res/values/strings.xml
+++ b/samples/browseable/ActiveNotifications/res/values/strings.xml
@@ -19,4 +19,5 @@
<string name="active_notifications">Active Notifications: %1$s</string>
<string name="update_notification_count">Update count</string>
<string name="sample_notification_content">This is a sample notification.</string>
+ <string name="sample_notification_summary_content">There are %s ActiveNotifications.</string>
</resources>
diff --git a/samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/ActiveNotificationFragment.java b/samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/ActiveNotificationFragment.java
deleted file mode 100644
index 3e87849..0000000
--- a/samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/ActiveNotificationFragment.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package com.example.android.activenotifications;
-/*
-* Copyright 2015 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.service.notification.StatusBarNotification;
-import android.support.v4.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.example.android.common.logger.Log;
-
-/**
- * A fragment that enables display of notifications.
- */
-public class ActiveNotificationFragment extends Fragment {
-
- private static final String TAG = "ActiveNotificationFragment";
-
- private NotificationManager mNotificationManager;
- private TextView mNumberOfNotifications;
-
- // Every notification needs a unique ID otherwise the previous one would be overwritten.
- private int mNotificationId = 0;
- private PendingIntent mDeletePendingIntent;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.fragment_notification_builder, container, false);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- updateNumberOfNotifications();
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- mNotificationManager = (NotificationManager) getActivity().getSystemService(
- Context.NOTIFICATION_SERVICE);
- mNumberOfNotifications = (TextView) view.findViewById(R.id.number_of_notifications);
-
- // Supply actions to the button that is displayed on screen.
- View.OnClickListener onClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.add_notification: {
- addNotificationAndReadNumber();
- break;
- }
- }
- }
- };
- view.findViewById(R.id.add_notification).setOnClickListener(onClickListener);
-
- // [BEGIN create_pending_intent_for_deletion]
- // Create a PendingIntent to be fired upon deletion of a Notification.
- Intent deleteIntent = new Intent(ActiveNotificationActivity.ACTION_NOTIFICATION_DELETE);
- mDeletePendingIntent = PendingIntent.getBroadcast(getActivity(),
- 2323 /* requestCode */, deleteIntent, 0);
- // [END create_pending_intent_for_deletion]
- }
-
- /**
- * Add a new {@link Notification} with sample data and send it to the system.
- * Then read the current number of displayed notifications for this application.
- */
- private void addNotificationAndReadNumber() {
- // [BEGIN create_notification]
- // Create a Notification and notify the system.
- final Notification.Builder builder = new Notification.Builder(getActivity())
- .setSmallIcon(R.mipmap.ic_notification)
- .setContentTitle(getString(R.string.app_name))
- .setContentText(getString(R.string.sample_notification_content))
- .setAutoCancel(true)
- .setDeleteIntent(mDeletePendingIntent);
-
- final Notification notification = builder.build();
- mNotificationManager.notify(++mNotificationId, notification);
- // [END create_notification]
- Log.i(TAG, "Add a notification");
- updateNumberOfNotifications();
- }
-
- /**
- * Request the current number of notifications from the {@link NotificationManager} and
- * display them to the user.
- */
- protected void updateNumberOfNotifications() {
- // [BEGIN get_active_notifications]
- // Query the currently displayed notifications.
- final StatusBarNotification[] activeNotifications = mNotificationManager
- .getActiveNotifications();
- // [END get_active_notifications]
- final int numberOfNotifications = activeNotifications.length;
- mNumberOfNotifications.setText(getString(R.string.active_notifications,
- numberOfNotifications));
- Log.i(TAG, getString(R.string.active_notifications, numberOfNotifications));
- }
-}
diff --git a/samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/ActiveNotificationActivity.java b/samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/ActiveNotificationsActivity.java
similarity index 90%
rename from samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/ActiveNotificationActivity.java
rename to samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/ActiveNotificationsActivity.java
index bdb5421..637478d 100644
--- a/samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/ActiveNotificationActivity.java
+++ b/samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/ActiveNotificationsActivity.java
@@ -23,9 +23,9 @@
import android.os.Bundle;
import android.os.PersistableBundle;
-public class ActiveNotificationActivity extends MainActivity {
+public class ActiveNotificationsActivity extends MainActivity {
- private ActiveNotificationFragment mFragment;
+ private ActiveNotificationsFragment mFragment;
protected static final String ACTION_NOTIFICATION_DELETE
= "com.example.android.activenotifications.delete";
@@ -48,7 +48,7 @@
}
private void findFragment() {
- mFragment = (ActiveNotificationFragment) getSupportFragmentManager()
+ mFragment = (ActiveNotificationsFragment) getSupportFragmentManager()
.findFragmentById(R.id.sample_content_fragment);
}
diff --git a/samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/ActiveNotificationsFragment.java b/samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/ActiveNotificationsFragment.java
new file mode 100644
index 0000000..db0dbac
--- /dev/null
+++ b/samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/ActiveNotificationsFragment.java
@@ -0,0 +1,193 @@
+package com.example.android.activenotifications;
+/*
+* Copyright 2015 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.service.notification.StatusBarNotification;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.NotificationCompat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.example.android.common.logger.Log;
+
+/**
+ * A fragment that allows notifications to be enqueued.
+ */
+public class ActiveNotificationsFragment extends Fragment {
+
+ /**
+ * The request code can be any number as long as it doesn't match another request code used
+ * in the same app.
+ */
+ private static final int REQUEST_CODE = 2323;
+
+ private static final String TAG = "ActiveNotificationsFragment";
+
+ private static final String NOTIFICATION_GROUP =
+ "com.example.android.activenotifications.notification_type";
+
+ private static final int NOTIFICATION_GROUP_SUMMARY_ID = 1;
+
+ private NotificationManager mNotificationManager;
+
+ private TextView mNumberOfNotifications;
+
+ // Every notification needs a unique ID otherwise the previous one would be overwritten. This
+ // variable is incremented when used.
+ private static int sNotificationId = NOTIFICATION_GROUP_SUMMARY_ID + 1;
+
+ private PendingIntent mDeletePendingIntent;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_notification_builder, container, false);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateNumberOfNotifications();
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ mNotificationManager = (NotificationManager) getActivity().getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ mNumberOfNotifications = (TextView) view.findViewById(R.id.number_of_notifications);
+
+ // Supply actions to the button that is displayed on screen.
+ View.OnClickListener onClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.add_notification: {
+ addNotificationAndUpdateSummaries();
+ break;
+ }
+ }
+ }
+ };
+ view.findViewById(R.id.add_notification).setOnClickListener(onClickListener);
+
+ // [BEGIN create_pending_intent_for_deletion]
+ // Create a PendingIntent to be fired upon deletion of a Notification.
+ Intent deleteIntent = new Intent(ActiveNotificationsActivity.ACTION_NOTIFICATION_DELETE);
+ mDeletePendingIntent = PendingIntent.getBroadcast(getActivity(),
+ REQUEST_CODE, deleteIntent, 0);
+ // [END create_pending_intent_for_deletion]
+ }
+
+ /**
+ * Adds a new {@link Notification} with sample data and sends it to the system.
+ * Then updates the current number of displayed notifications for this application and
+ * creates a notification summary if more than one notification exists.
+ */
+ private void addNotificationAndUpdateSummaries() {
+ // [BEGIN create_notification]
+ // Create a Notification and notify the system.
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(getActivity())
+ .setSmallIcon(R.mipmap.ic_notification)
+ .setContentTitle(getString(R.string.app_name))
+ .setContentText(getString(R.string.sample_notification_content))
+ .setAutoCancel(true)
+ .setDeleteIntent(mDeletePendingIntent)
+ .setGroup(NOTIFICATION_GROUP);
+
+ final Notification notification = builder.build();
+ mNotificationManager.notify(getNewNotificationId(), notification);
+ // [END create_notification]
+ Log.i(TAG, "Add a notification");
+
+ updateNotificationSummary();
+ updateNumberOfNotifications();
+ }
+
+ /**
+ * Adds/updates/removes the notification summary as necessary.
+ */
+ protected void updateNotificationSummary() {
+ final StatusBarNotification[] activeNotifications = mNotificationManager
+ .getActiveNotifications();
+
+ int numberOfNotifications = activeNotifications.length;
+ // Since the notifications might include a summary notification remove it from the count if
+ // it is present.
+ for (StatusBarNotification notification : activeNotifications) {
+ if (notification.getId() == NOTIFICATION_GROUP_SUMMARY_ID) {
+ numberOfNotifications--;
+ break;
+ }
+ }
+
+ if (numberOfNotifications > 1) {
+ // Add/update the notification summary.
+ String notificationContent = getString(R.string.sample_notification_summary_content,
+ "" + numberOfNotifications);
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(getActivity())
+ .setSmallIcon(R.mipmap.ic_notification)
+ .setStyle(new NotificationCompat.BigTextStyle()
+ .setSummaryText(notificationContent))
+ .setGroup(NOTIFICATION_GROUP)
+ .setGroupSummary(true);
+ final Notification notification = builder.build();
+ mNotificationManager.notify(NOTIFICATION_GROUP_SUMMARY_ID, notification);
+ } else {
+ // Remove the notification summary.
+ mNotificationManager.cancel(NOTIFICATION_GROUP_SUMMARY_ID);
+ }
+ }
+
+ /**
+ * Requests the current number of notifications from the {@link NotificationManager} and
+ * display them to the user.
+ */
+ protected void updateNumberOfNotifications() {
+ // [BEGIN get_active_notifications]
+ // Query the currently displayed notifications.
+ final StatusBarNotification[] activeNotifications = mNotificationManager
+ .getActiveNotifications();
+ // [END get_active_notifications]
+ final int numberOfNotifications = activeNotifications.length;
+ mNumberOfNotifications.setText(getString(R.string.active_notifications,
+ numberOfNotifications));
+ Log.i(TAG, getString(R.string.active_notifications, numberOfNotifications));
+ }
+
+ /**
+ * Retrieves a unique notification ID.
+ */
+ public int getNewNotificationId() {
+ int notificationId = sNotificationId++;
+
+ // Unlikely in the sample, but the int will overflow if used enough so we skip the summary
+ // ID. Most apps will prefer a more deterministic way of identifying an ID such as hashing
+ // the content of the notification.
+ if (notificationId == NOTIFICATION_GROUP_SUMMARY_ID) {
+ notificationId = sNotificationId++;
+ }
+ return notificationId;
+ }
+}
diff --git a/samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/MainActivity.java b/samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/MainActivity.java
index bf11b8e..965248f 100644
--- a/samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/MainActivity.java
+++ b/samples/browseable/ActiveNotifications/src/com.example.android.activenotifications/MainActivity.java
@@ -50,7 +50,7 @@
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- ActiveNotificationFragment fragment = new ActiveNotificationFragment();
+ ActiveNotificationsFragment fragment = new ActiveNotificationsFragment();
transaction.replace(R.id.sample_content_fragment, fragment);
transaction.commit();
}
diff --git a/samples/browseable/AutoBackupForApps/src/com.example.android.autobackupsample/AddFileActivity.java b/samples/browseable/AutoBackupForApps/src/com.example.android.autobackupsample/AddFileActivity.java
index 64f2912..d026ddd 100644
--- a/samples/browseable/AutoBackupForApps/src/com.example.android.autobackupsample/AddFileActivity.java
+++ b/samples/browseable/AutoBackupForApps/src/com.example.android.autobackupsample/AddFileActivity.java
@@ -161,7 +161,7 @@
return;
}
- long fileSize = Long.parseLong(fileSizeEditTextValue) * mSizeMultiplier;
+ long fileSize = Integer.parseInt(fileSizeEditTextValue) * mSizeMultiplier;
if (mFileStorage == FileStorage.EXTERNAL && !Utils.isExternalStorageAvailable()) {
Toast toast = Toast.makeText(this,
diff --git a/samples/browseable/Camera2Raw/src/com.example.android.camera2raw/Camera2RawFragment.java b/samples/browseable/Camera2Raw/src/com.example.android.camera2raw/Camera2RawFragment.java
index bf5efe5..341cc72 100644
--- a/samples/browseable/Camera2Raw/src/com.example.android.camera2raw/Camera2RawFragment.java
+++ b/samples/browseable/Camera2Raw/src/com.example.android.camera2raw/Camera2RawFragment.java
@@ -442,7 +442,10 @@
case STATE_WAITING_FOR_3A_CONVERGENCE: {
boolean readyToCapture = true;
if (!mNoAFRun) {
- int afState = result.get(CaptureResult.CONTROL_AF_STATE);
+ Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
+ if (afState == null) {
+ break;
+ }
// If auto-focus has reached locked state, we are ready to capture
readyToCapture =
@@ -454,8 +457,11 @@
// auto-exposure and auto-white-balance have converged as well before
// taking a picture.
if (!isLegacyLocked()) {
- int aeState = result.get(CaptureResult.CONTROL_AE_STATE);
- int awbState = result.get(CaptureResult.CONTROL_AWB_STATE);
+ Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+ Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE);
+ if (aeState == null || awbState == null) {
+ break;
+ }
readyToCapture = readyToCapture &&
aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED &&
diff --git a/samples/browseable/Camera2Video/src/com.example.android.camera2video/Camera2VideoFragment.java b/samples/browseable/Camera2Video/src/com.example.android.camera2video/Camera2VideoFragment.java
index d191e77..e2853b3 100644
--- a/samples/browseable/Camera2Video/src/com.example.android.camera2video/Camera2VideoFragment.java
+++ b/samples/browseable/Camera2Video/src/com.example.android.camera2video/Camera2VideoFragment.java
@@ -757,4 +757,4 @@
}
-}
\ No newline at end of file
+}
diff --git a/samples/browseable/DirectBoot/AndroidManifest.xml b/samples/browseable/DirectBoot/AndroidManifest.xml
new file mode 100644
index 0000000..be3be93
--- /dev/null
+++ b/samples/browseable/DirectBoot/AndroidManifest.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?><!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.directboot"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+
+ <application
+ android:allowBackup="true"
+ android:label="@string/app_name"
+ android:icon="@mipmap/ic_launcher"
+ android:theme="@style/Theme.AppCompat.Light"
+ >
+
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <receiver android:name=".BootBroadcastReceiver"
+ android:exported="false"
+ android:directBootAware="true">
+ <!-- Listening the BOOT_COMPLETED action for legacy pre-N devices -->
+ <intent-filter>
+ <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ </receiver>
+
+ <service android:name=".alarms.AlarmIntentService"
+ android:exported="false"
+ android:directBootAware="true"/>
+ </application>
+</manifest>
diff --git a/samples/browseable/DirectBoot/_index.jd b/samples/browseable/DirectBoot/_index.jd
new file mode 100644
index 0000000..8c60f2f
--- /dev/null
+++ b/samples/browseable/DirectBoot/_index.jd
@@ -0,0 +1,13 @@
+
+page.tags="DirectBoot"
+sample.group=Android N Preview
+@jd:body
+
+<p>
+
+This sample demonstrates how to store/access data in a device protected storage
+which is always available while the device is booted.
+This sample works as a simple alarm clock. On > Android N devices, the scheduled alarms
+go off after reboot even before the user enters their credentials.
+
+ </p>
diff --git a/samples/browseable/DirectBoot/res/drawable-hdpi/ic_add_alarm_white_24dp.png b/samples/browseable/DirectBoot/res/drawable-hdpi/ic_add_alarm_white_24dp.png
new file mode 100644
index 0000000..5f33b37
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-hdpi/ic_add_alarm_white_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-hdpi/ic_alarm_black_24dp.png b/samples/browseable/DirectBoot/res/drawable-hdpi/ic_alarm_black_24dp.png
new file mode 100644
index 0000000..9074911
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-hdpi/ic_alarm_black_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-hdpi/ic_delete_black_24dp.png b/samples/browseable/DirectBoot/res/drawable-hdpi/ic_delete_black_24dp.png
new file mode 100644
index 0000000..dbbb602
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-hdpi/ic_delete_black_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-hdpi/ic_fbe_notification.png b/samples/browseable/DirectBoot/res/drawable-hdpi/ic_fbe_notification.png
new file mode 100644
index 0000000..421baf4
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-hdpi/ic_fbe_notification.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-hdpi/tile.9.png b/samples/browseable/DirectBoot/res/drawable-hdpi/tile.9.png
new file mode 100644
index 0000000..1358628
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-hdpi/tile.9.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-mdpi/ic_add_alarm_white_24dp.png b/samples/browseable/DirectBoot/res/drawable-mdpi/ic_add_alarm_white_24dp.png
new file mode 100644
index 0000000..85a1e17
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-mdpi/ic_add_alarm_white_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-mdpi/ic_alarm_black_24dp.png b/samples/browseable/DirectBoot/res/drawable-mdpi/ic_alarm_black_24dp.png
new file mode 100644
index 0000000..ed2f90a
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-mdpi/ic_alarm_black_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-mdpi/ic_delete_black_24dp.png b/samples/browseable/DirectBoot/res/drawable-mdpi/ic_delete_black_24dp.png
new file mode 100644
index 0000000..999aa4c
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-mdpi/ic_delete_black_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-mdpi/ic_fbe_notification.png b/samples/browseable/DirectBoot/res/drawable-mdpi/ic_fbe_notification.png
new file mode 100644
index 0000000..5635e41
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-mdpi/ic_fbe_notification.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-xhdpi/ic_add_alarm_white_24dp.png b/samples/browseable/DirectBoot/res/drawable-xhdpi/ic_add_alarm_white_24dp.png
new file mode 100644
index 0000000..90f4de4
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-xhdpi/ic_add_alarm_white_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-xhdpi/ic_alarm_black_24dp.png b/samples/browseable/DirectBoot/res/drawable-xhdpi/ic_alarm_black_24dp.png
new file mode 100644
index 0000000..e37eab0
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-xhdpi/ic_alarm_black_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-xhdpi/ic_delete_black_24dp.png b/samples/browseable/DirectBoot/res/drawable-xhdpi/ic_delete_black_24dp.png
new file mode 100644
index 0000000..796ccd2
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-xhdpi/ic_delete_black_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-xhdpi/ic_fbe_notification.png b/samples/browseable/DirectBoot/res/drawable-xhdpi/ic_fbe_notification.png
new file mode 100644
index 0000000..e07c385
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-xhdpi/ic_fbe_notification.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-xxhdpi/ic_add_alarm_white_24dp.png b/samples/browseable/DirectBoot/res/drawable-xxhdpi/ic_add_alarm_white_24dp.png
new file mode 100644
index 0000000..3319384
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-xxhdpi/ic_add_alarm_white_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-xxhdpi/ic_alarm_black_24dp.png b/samples/browseable/DirectBoot/res/drawable-xxhdpi/ic_alarm_black_24dp.png
new file mode 100644
index 0000000..eb1d08d
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-xxhdpi/ic_alarm_black_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-xxhdpi/ic_delete_black_24dp.png b/samples/browseable/DirectBoot/res/drawable-xxhdpi/ic_delete_black_24dp.png
new file mode 100644
index 0000000..6d7cb81
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-xxhdpi/ic_delete_black_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-xxhdpi/ic_fbe_notification.png b/samples/browseable/DirectBoot/res/drawable-xxhdpi/ic_fbe_notification.png
new file mode 100644
index 0000000..193d1bc
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-xxhdpi/ic_fbe_notification.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-xxxhdpi/ic_add_alarm_white_24dp.png b/samples/browseable/DirectBoot/res/drawable-xxxhdpi/ic_add_alarm_white_24dp.png
new file mode 100644
index 0000000..2e425c0
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-xxxhdpi/ic_add_alarm_white_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-xxxhdpi/ic_alarm_black_24dp.png b/samples/browseable/DirectBoot/res/drawable-xxxhdpi/ic_alarm_black_24dp.png
new file mode 100644
index 0000000..04b6c71
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-xxxhdpi/ic_alarm_black_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-xxxhdpi/ic_delete_black_24dp.png b/samples/browseable/DirectBoot/res/drawable-xxxhdpi/ic_delete_black_24dp.png
new file mode 100644
index 0000000..f2b75c3
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-xxxhdpi/ic_delete_black_24dp.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable-xxxhdpi/ic_fbe_notification.png b/samples/browseable/DirectBoot/res/drawable-xxxhdpi/ic_fbe_notification.png
new file mode 100644
index 0000000..5dd463f
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable-xxxhdpi/ic_fbe_notification.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/drawable/divider.xml b/samples/browseable/DirectBoot/res/drawable/divider.xml
new file mode 100644
index 0000000..b21e664
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/drawable/divider.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="1dp"
+ android:height="1dp" />
+ <solid android:color="@android:color/darker_gray" />
+</shape>
\ No newline at end of file
diff --git a/samples/browseable/DirectBoot/res/layout/activity_main.xml b/samples/browseable/DirectBoot/res/layout/activity_main.xml
new file mode 100755
index 0000000..4011cc4
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/layout/activity_main.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="com.example.android.directboot.MainActivity"
+ tools:ignore="MergeRootFrame" />
diff --git a/samples/browseable/DirectBoot/res/layout/alarm_row.xml b/samples/browseable/DirectBoot/res/layout/alarm_row.xml
new file mode 100644
index 0000000..ab5fb88
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/layout/alarm_row.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2016 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/alarm_row_height"
+ android:layout_marginLeft="@dimen/margin_medium"
+ android:layout_marginRight="@dimen/margin_medium" >
+
+ <TextView
+ android:id="@+id/text_alarm_time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/margin_medium"
+ android:textSize="52sp"
+ />
+
+ <TextView
+ android:id="@+id/text_alarm_date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@id/text_alarm_time"
+ android:layout_marginStart="@dimen/margin_small"
+ android:layout_alignBaseline="@id/text_alarm_time"
+ android:textSize="20sp"
+ />
+
+ <ImageView
+ android:id="@+id/image_delete_alarm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/margin_medium"
+ android:layout_centerVertical="true"
+ android:layout_alignParentEnd="true"
+ android:src="@drawable/ic_delete_black_24dp"
+ />
+</RelativeLayout>
\ No newline at end of file
diff --git a/samples/browseable/DirectBoot/res/layout/fragment_alarm_scheduler.xml b/samples/browseable/DirectBoot/res/layout/fragment_alarm_scheduler.xml
new file mode 100644
index 0000000..68fea72
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/layout/fragment_alarm_scheduler.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <TextView
+ android:id="@+id/text_registered_alarms"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/margin_small"
+ android:drawableTop="@drawable/ic_alarm_black_24dp"
+ android:text="@string/registered_alarms"
+ android:textSize="14sp"
+ android:gravity="center"
+ />
+
+ <TextView
+ android:id="@+id/text_intro_message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_medium"
+ android:layout_below="@id/text_registered_alarms"
+ android:text="@string/intro_message"
+ android:textSize="20sp"
+ android:visibility="gone"
+ />
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/recycler_view_alarms"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_below="@id/text_intro_message"
+ />
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab_add_alarm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end|bottom"
+ android:layout_margin="@dimen/margin_large"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentBottom="true"
+ android:src="@drawable/ic_add_alarm_white_24dp" />
+</RelativeLayout>
diff --git a/samples/browseable/DirectBoot/res/layout/fragment_time_picker.xml b/samples/browseable/DirectBoot/res/layout/fragment_time_picker.xml
new file mode 100644
index 0000000..cb83530
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/layout/fragment_time_picker.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TimePicker
+ android:id="@+id/time_picker_alarm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:id="@+id/button_panel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/margin_medium"
+ android:paddingEnd="@dimen/margin_medium"
+ android:paddingTop="@dimen/margin_small"
+ android:paddingBottom="@dimen/margin_small"
+ android:gravity="bottom"
+ style="?android:attr/buttonBarStyle">
+
+ <Space
+ android:id="@+id/spacer"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="invisible" />
+
+ <Button
+ android:id="@+id/button_cancel_time_picker"
+ style="?android:attr/buttonBarNegativeButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/cancel"
+ />
+
+ <Button
+ android:id="@+id/button_ok_time_picker"
+ style="?android:attr/buttonBarPositiveButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/ok"
+ />
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/samples/browseable/DirectBoot/res/mipmap-hdpi/ic_launcher.png b/samples/browseable/DirectBoot/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..da7ca83
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/mipmap-mdpi/ic_launcher.png b/samples/browseable/DirectBoot/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..b563d2a
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/mipmap-xhdpi/ic_launcher.png b/samples/browseable/DirectBoot/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..d8b1bc2
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/mipmap-xxhdpi/ic_launcher.png b/samples/browseable/DirectBoot/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..da95a69
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/mipmap-xxxhdpi/ic_launcher.png b/samples/browseable/DirectBoot/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..59c453d
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/DirectBoot/res/values-sw600dp/template-dimens.xml b/samples/browseable/DirectBoot/res/values-sw600dp/template-dimens.xml
new file mode 100644
index 0000000..22074a2
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/values-sw600dp/template-dimens.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <!-- Semantic definitions -->
+
+ <dimen name="horizontal_page_margin">@dimen/margin_huge</dimen>
+ <dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
+
+</resources>
diff --git a/samples/browseable/DirectBoot/res/values-sw600dp/template-styles.xml b/samples/browseable/DirectBoot/res/values-sw600dp/template-styles.xml
new file mode 100644
index 0000000..03d1974
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/values-sw600dp/template-styles.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <style name="Widget.SampleMessage">
+ <item name="android:textAppearance">?android:textAppearanceLarge</item>
+ <item name="android:lineSpacingMultiplier">1.2</item>
+ <item name="android:shadowDy">-6.5</item>
+ </style>
+
+</resources>
diff --git a/samples/browseable/DirectBoot/res/values-v11/template-styles.xml b/samples/browseable/DirectBoot/res/values-v11/template-styles.xml
new file mode 100644
index 0000000..8c1ea66
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/values-v11/template-styles.xml
@@ -0,0 +1,22 @@
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <!-- Activity themes -->
+ <style name="Theme.Base" parent="android:Theme.Holo.Light" />
+
+</resources>
diff --git a/samples/browseable/DirectBoot/res/values-v21/base-colors.xml b/samples/browseable/DirectBoot/res/values-v21/base-colors.xml
new file mode 100644
index 0000000..8b6ec3f
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/values-v21/base-colors.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+
+
+</resources>
diff --git a/samples/browseable/DirectBoot/res/values-v21/base-template-styles.xml b/samples/browseable/DirectBoot/res/values-v21/base-template-styles.xml
new file mode 100644
index 0000000..c778e4f
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/values-v21/base-template-styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+
+ <!-- Activity themes -->
+ <style name="Theme.Base" parent="android:Theme.Material.Light">
+ </style>
+
+</resources>
diff --git a/samples/browseable/DirectBoot/res/values/base-strings.xml b/samples/browseable/DirectBoot/res/values/base-strings.xml
new file mode 100644
index 0000000..c84111b
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/values/base-strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="app_name">DirectBoot</string>
+ <string name="intro_message">
+ <![CDATA[
+
+
+This sample demonstrates how to store/access data in a device protected storage
+which is always available while the device is booted.
+This sample works as a simple alarm clock. On > Android N devices, the scheduled alarms
+go off after reboot even before the user enters their credentials.
+
+
+ ]]>
+ </string>
+</resources>
diff --git a/samples/browseable/DirectBoot/res/values/dimens.xml b/samples/browseable/DirectBoot/res/values/dimens.xml
new file mode 100644
index 0000000..cf23df4
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<!--
+ Copyright 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <dimen name="alarm_row_height">80dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/samples/browseable/DirectBoot/res/values/strings.xml b/samples/browseable/DirectBoot/res/values/strings.xml
new file mode 100644
index 0000000..f8db16c
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/values/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="alarm_saved">Alarm scheduled at %1$2d:%2$02d</string>
+ <string name="alarm_deleted">Deleted the alarm at %1$2d:%2$02d</string>
+ <string name="alarm_went_off">Alarm went off at %1$2d:%2$02d</string>
+ <string name="time_24hour_format">%1$2d:%2$02d</string>
+ <string name="ok">OK</string>
+ <string name="cancel">Cancel</string>
+ <string name="registered_alarms">Registered alarms</string>
+</resources>
\ No newline at end of file
diff --git a/samples/browseable/DirectBoot/res/values/template-dimens.xml b/samples/browseable/DirectBoot/res/values/template-dimens.xml
new file mode 100644
index 0000000..39e710b
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/values/template-dimens.xml
@@ -0,0 +1,32 @@
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <!-- Define standard dimensions to comply with Holo-style grids and rhythm. -->
+
+ <dimen name="margin_tiny">4dp</dimen>
+ <dimen name="margin_small">8dp</dimen>
+ <dimen name="margin_medium">16dp</dimen>
+ <dimen name="margin_large">32dp</dimen>
+ <dimen name="margin_huge">64dp</dimen>
+
+ <!-- Semantic definitions -->
+
+ <dimen name="horizontal_page_margin">@dimen/margin_medium</dimen>
+ <dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
+
+</resources>
diff --git a/samples/browseable/DirectBoot/res/values/template-styles.xml b/samples/browseable/DirectBoot/res/values/template-styles.xml
new file mode 100644
index 0000000..6e7d593
--- /dev/null
+++ b/samples/browseable/DirectBoot/res/values/template-styles.xml
@@ -0,0 +1,42 @@
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <!-- Activity themes -->
+
+ <style name="Theme.Base" parent="android:Theme.Light" />
+
+ <style name="Theme.Sample" parent="Theme.Base" />
+
+ <style name="AppTheme" parent="Theme.Sample" />
+ <!-- Widget styling -->
+
+ <style name="Widget" />
+
+ <style name="Widget.SampleMessage">
+ <item name="android:textAppearance">?android:textAppearanceMedium</item>
+ <item name="android:lineSpacingMultiplier">1.1</item>
+ </style>
+
+ <style name="Widget.SampleMessageTile">
+ <item name="android:background">@drawable/tile</item>
+ <item name="android:shadowColor">#7F000000</item>
+ <item name="android:shadowDy">-3.5</item>
+ <item name="android:shadowRadius">2</item>
+ </style>
+
+</resources>
diff --git a/samples/browseable/DirectBoot/src/com.example.android.directboot/BootBroadcastReceiver.java b/samples/browseable/DirectBoot/src/com.example.android.directboot/BootBroadcastReceiver.java
new file mode 100644
index 0000000..6ba78f4
--- /dev/null
+++ b/samples/browseable/DirectBoot/src/com.example.android.directboot/BootBroadcastReceiver.java
@@ -0,0 +1,64 @@
+/*
+* Copyright 2016 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT 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.directboot;
+
+import com.example.android.directboot.alarms.Alarm;
+import com.example.android.directboot.alarms.AlarmStorage;
+import com.example.android.directboot.alarms.AlarmUtil;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.support.v4.os.BuildCompat;
+import android.support.v4.os.UserManagerCompat;
+import android.util.Log;
+
+/**
+ * BroadcastReceiver that receives the following implicit broadcasts:
+ * <ul>
+ * <li>Intent.ACTION_BOOT_COMPLETED</li>
+ * <li>Intent.ACTION_LOCKED_BOOT_COMPLETED</li>
+ * </ul>
+ *
+ * To receive the Intent.ACTION_LOCKED_BOOT_COMPLETED broadcast, the receiver needs to have
+ * <code>directBootAware="true"</code> property in the manifest.
+ */
+public class BootBroadcastReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "BootBroadcastReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ boolean bootCompleted;
+ String action = intent.getAction();
+ Log.i(TAG, "Received action: " + action + ", user unlocked: " + UserManagerCompat
+ .isUserUnlocked(context));
+ if (BuildCompat.isAtLeastN()) {
+ bootCompleted = Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action);
+ } else {
+ bootCompleted = Intent.ACTION_BOOT_COMPLETED.equals(action);
+ }
+ if (!bootCompleted) {
+ return;
+ }
+ AlarmUtil util = new AlarmUtil(context);
+ AlarmStorage alarmStorage = new AlarmStorage(context);
+ for (Alarm alarm : alarmStorage.getAlarms()) {
+ util.scheduleAlarm(alarm);
+ }
+ }
+}
diff --git a/samples/browseable/DirectBoot/src/com.example.android.directboot/MainActivity.java b/samples/browseable/DirectBoot/src/com.example.android.directboot/MainActivity.java
new file mode 100644
index 0000000..1b16ee5
--- /dev/null
+++ b/samples/browseable/DirectBoot/src/com.example.android.directboot/MainActivity.java
@@ -0,0 +1,37 @@
+/*
+* Copyright 2016 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT 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.directboot;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+
+/**
+ * Launcher Activity for the Direct Boot sample app.
+ */
+public class MainActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ if (savedInstanceState == null) {
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.container, SchedulerFragment.newInstance())
+ .commit();
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/browseable/DirectBoot/src/com.example.android.directboot/SchedulerFragment.java b/samples/browseable/DirectBoot/src/com.example.android.directboot/SchedulerFragment.java
new file mode 100644
index 0000000..0f32c25
--- /dev/null
+++ b/samples/browseable/DirectBoot/src/com.example.android.directboot/SchedulerFragment.java
@@ -0,0 +1,136 @@
+/*
+* Copyright 2016 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT 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.directboot;
+
+import com.example.android.directboot.alarms.Alarm;
+import com.example.android.directboot.alarms.AlarmAdapter;
+import com.example.android.directboot.alarms.AlarmIntentService;
+import com.example.android.directboot.alarms.AlarmStorage;
+import com.example.android.directboot.alarms.AlarmUtil;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * Fragment that registers scheduled alarms.
+ */
+public class SchedulerFragment extends Fragment {
+
+ private static final String FRAGMENT_TIME_PICKER_TAG = "fragment_time_picker";
+
+ private AlarmAdapter mAlarmAdapter;
+ private AlarmUtil mAlarmUtil;
+ private TextView mTextViewIntroMessage;
+ private BroadcastReceiver mAlarmWentOffBroadcastReceiver;
+
+ public static SchedulerFragment newInstance() {
+ SchedulerFragment fragment = new SchedulerFragment();
+ return fragment;
+ }
+
+ public SchedulerFragment() {
+ // Required empty public constructor
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mAlarmWentOffBroadcastReceiver = new AlarmWentOffReceiver();
+ LocalBroadcastManager.getInstance(getActivity())
+ .registerReceiver(mAlarmWentOffBroadcastReceiver,
+ new IntentFilter(AlarmIntentService.ALARM_WENT_OFF_ACTION));
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_alarm_scheduler, container, false);
+ }
+
+ @Override
+ public void onViewCreated(final View rootView, Bundle savedInstanceState) {
+ super.onViewCreated(rootView, savedInstanceState);
+ FloatingActionButton fab = (FloatingActionButton) rootView.findViewById(R.id.fab_add_alarm);
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ TimePickerFragment fragment = TimePickerFragment.newInstance();
+ fragment.setAlarmAddListener(new AlarmAddListenerImpl());
+ fragment.show(getFragmentManager(), FRAGMENT_TIME_PICKER_TAG);
+ }
+ });
+ mTextViewIntroMessage = (TextView) rootView.findViewById(R.id.text_intro_message);
+ Activity activity = getActivity();
+ AlarmStorage alarmStorage = new AlarmStorage(activity);
+ mAlarmAdapter = new AlarmAdapter(activity, alarmStorage.getAlarms());
+ if (mAlarmAdapter.getItemCount() == 0) {
+ mTextViewIntroMessage.setVisibility(View.VISIBLE);
+ }
+ RecyclerView recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view_alarms);
+ recyclerView.setLayoutManager(new LinearLayoutManager(activity));
+ recyclerView.setAdapter(mAlarmAdapter);
+ recyclerView.addItemDecoration(new AlarmAdapter.DividerItemDecoration(activity));
+ mAlarmUtil = new AlarmUtil(activity);
+ }
+
+ @Override
+ public void onDestroy() {
+ LocalBroadcastManager.getInstance(getActivity())
+ .unregisterReceiver(mAlarmWentOffBroadcastReceiver);
+ super.onDestroy();
+ }
+
+ /**
+ * {@link TimePickerFragment.AlarmAddListener} to do actions after an alarm is added.
+ */
+ private class AlarmAddListenerImpl implements TimePickerFragment.AlarmAddListener {
+
+ @Override
+ public void onAlarmAdded(Alarm alarm) {
+ mAlarmAdapter.addAlarm(alarm);
+ mAlarmUtil.scheduleAlarm(alarm);
+ mTextViewIntroMessage.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * A {@link BroadcastReceiver} that receives an intent when an alarm goes off.
+ * This receiver removes the corresponding alarm from the RecyclerView.
+ */
+ private class AlarmWentOffReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Alarm alarm = intent.getParcelableExtra(AlarmIntentService.ALARM_KEY);
+ mAlarmAdapter.deleteAlarm(alarm);
+ }
+ }
+}
diff --git a/samples/browseable/DirectBoot/src/com.example.android.directboot/TimePickerFragment.java b/samples/browseable/DirectBoot/src/com.example.android.directboot/TimePickerFragment.java
new file mode 100644
index 0000000..5715668
--- /dev/null
+++ b/samples/browseable/DirectBoot/src/com.example.android.directboot/TimePickerFragment.java
@@ -0,0 +1,105 @@
+/*
+* Copyright 2016 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT 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.directboot;
+
+import com.example.android.directboot.alarms.Alarm;
+import com.example.android.directboot.alarms.AlarmStorage;
+import com.example.android.directboot.alarms.AlarmUtil;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TimePicker;
+import android.widget.Toast;
+
+import java.util.Calendar;
+
+/**
+ * DialogFragment for showing a TimePicker.
+ */
+public class TimePickerFragment extends DialogFragment {
+
+ private TimePicker mTimePicker;
+ private AlarmStorage mAlarmStorage;
+ private AlarmAddListener mAlarmAddListener;
+ private AlarmUtil mAlarmUtil;
+
+ public TimePickerFragment() {}
+
+ public static TimePickerFragment newInstance() {
+ return new TimePickerFragment();
+ }
+
+ public void setAlarmAddListener(AlarmAddListener listener) {
+ mAlarmAddListener = listener;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Material_Light_Dialog);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mAlarmStorage = new AlarmStorage(getActivity());
+ mAlarmUtil = new AlarmUtil(getActivity());
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_time_picker, container, false);
+ mTimePicker = (TimePicker) view.findViewById(R.id.time_picker_alarm);
+ Button buttonOk = (Button) view.findViewById(R.id.button_ok_time_picker);
+ buttonOk.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Calendar alarmTime = mAlarmUtil
+ .getNextAlarmTime(mTimePicker.getHour(), mTimePicker.getMinute());
+ Alarm alarm = mAlarmStorage
+ .saveAlarm(alarmTime.get(Calendar.MONTH), alarmTime.get(Calendar.DATE),
+ alarmTime.get(Calendar.HOUR_OF_DAY), alarmTime.get(Calendar.MINUTE));
+ String alarmSavedString = getActivity()
+ .getString(R.string.alarm_saved, alarm.hour, alarm.minute);
+ Toast.makeText(getActivity(), alarmSavedString, Toast.LENGTH_SHORT).show();
+ if (mAlarmAddListener != null) {
+ mAlarmAddListener.onAlarmAdded(alarm);
+ }
+ dismiss();
+ }
+ });
+ Button buttonCancel = (Button) view.findViewById(R.id.button_cancel_time_picker);
+ buttonCancel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ dismiss();
+ }
+ });
+ return view;
+ }
+
+ public interface AlarmAddListener {
+ void onAlarmAdded(Alarm alarm);
+ }
+}
diff --git a/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/Alarm.java b/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/Alarm.java
new file mode 100644
index 0000000..82712c6
--- /dev/null
+++ b/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/Alarm.java
@@ -0,0 +1,165 @@
+/*
+* Copyright 2016 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT 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.directboot.alarms;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Calendar;
+import java.util.Objects;
+
+/**
+ * Class represents a single alarm.
+ */
+public class Alarm implements Comparable<Alarm>, Parcelable {
+
+ public int id;
+ public int month;
+ public int date;
+ /** Integer as a 24-hour format */
+ public int hour;
+ public int minute;
+
+ public Alarm() {}
+
+ protected Alarm(Parcel in) {
+ id = in.readInt();
+ month = in.readInt();
+ date = in.readInt();
+ hour = in.readInt();
+ minute = in.readInt();
+ }
+
+ public static final Creator<Alarm> CREATOR = new Creator<Alarm>() {
+ @Override
+ public Alarm createFromParcel(Parcel in) {
+ return new Alarm(in);
+ }
+
+ @Override
+ public Alarm[] newArray(int size) {
+ return new Alarm[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ parcel.writeInt(id);
+ parcel.writeInt(month);
+ parcel.writeInt(date);
+ parcel.writeInt(hour);
+ parcel.writeInt(minute);
+ }
+
+ /**
+ * Serialize the instance as a JSON String.
+ * @return serialized JSON String.
+ */
+ public String toJson() {
+ JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put("id", id);
+ jsonObject.put("month", month);
+ jsonObject.put("date", date);
+ jsonObject.put("hour", hour);
+ jsonObject.put("minute", minute);
+ } catch (JSONException e) {
+ throw new IllegalStateException("Failed to convert the object to JSON");
+ }
+ return jsonObject.toString();
+ }
+
+ /**
+ * Parses a Json string to an {@link Alarm} instance.
+ *
+ * @param string The String representation of an alarm
+ * @return an instance of {@link Alarm}
+ */
+ public static Alarm fromJson(String string) {
+ JSONObject jsonObject;
+ Alarm alarm = new Alarm();
+ try {
+ jsonObject = new JSONObject(string);
+ alarm.id = jsonObject.getInt("id");
+ alarm.month = jsonObject.getInt("month");
+ alarm.date = jsonObject.getInt("date");
+ alarm.hour = jsonObject.getInt("hour");
+ alarm.minute = jsonObject.getInt("minute");
+ } catch (JSONException e) {
+ throw new IllegalArgumentException("Failed to parse the String: " + string);
+ }
+
+ return alarm;
+ }
+
+ @Override
+ public String toString() {
+ return "Alarm{" +
+ "id=" + id +
+ ", month=" + month +
+ ", date=" + date +
+ ", hour=" + hour +
+ ", minute=" + minute +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Alarm)) {
+ return false;
+ }
+ Alarm alarm = (Alarm) o;
+ return id == alarm.id &&
+ month == alarm.month &&
+ date == alarm.date &&
+ hour == alarm.hour &&
+ minute == alarm.minute;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, month, date, hour, minute);
+ }
+
+ @Override
+ public int compareTo(@NonNull Alarm other) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.MONTH, month);
+ calendar.set(Calendar.DATE, date);
+ calendar.set(Calendar.HOUR_OF_DAY, hour);
+ calendar.set(Calendar.MINUTE, minute);
+
+ Calendar otherCal = Calendar.getInstance();
+ otherCal.set(Calendar.MONTH, other.month);
+ otherCal.set(Calendar.DATE, other.date);
+ otherCal.set(Calendar.HOUR_OF_DAY, other.hour);
+ otherCal.set(Calendar.MINUTE, other.minute);
+ return calendar.compareTo(otherCal);
+ }
+}
diff --git a/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/AlarmAdapter.java b/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/AlarmAdapter.java
new file mode 100644
index 0000000..70582cb
--- /dev/null
+++ b/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/AlarmAdapter.java
@@ -0,0 +1,189 @@
+/*
+* Copyright 2016 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT 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.directboot.alarms;
+
+import com.example.android.directboot.R;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.support.v7.util.SortedList;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Adapter responsible for interactions between the {@link RecyclerView} and the
+ * scheduled alarms.
+ */
+public class AlarmAdapter extends RecyclerView.Adapter<AlarmAdapter.AlarmViewHolder> {
+
+ private SortedList<Alarm> mAlarmList;
+ private AlarmStorage mAlarmStorage;
+ private AlarmUtil mAlarmUtil;
+ private DateFormat mDateFormat;
+ private DateFormat mTimeFormat;
+ private Context mContext;
+
+ public AlarmAdapter(Context context, Set<Alarm> alarms) {
+ mAlarmList = new SortedList<>(Alarm.class, new SortedListCallback());
+ mAlarmList.addAll(alarms);
+ mAlarmStorage = new AlarmStorage(context);
+ mContext = context;
+ mAlarmUtil = new AlarmUtil(context);
+ mDateFormat = new SimpleDateFormat("MMM dd", Locale.getDefault());
+ mTimeFormat = new SimpleDateFormat("kk:mm", Locale.getDefault());
+ }
+
+ @Override
+ public AlarmViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.alarm_row, parent, false);
+ return new AlarmViewHolder(v);
+ }
+
+ @Override
+ public void onBindViewHolder(AlarmViewHolder holder, final int position) {
+ Alarm alarm = mAlarmList.get(position);
+ Calendar alarmTime = Calendar.getInstance();
+ alarmTime.set(Calendar.MONTH, alarm.month);
+ alarmTime.set(Calendar.DATE, alarm.date);
+ alarmTime.set(Calendar.HOUR_OF_DAY, alarm.hour);
+ alarmTime.set(Calendar.MINUTE, alarm.minute);
+ holder.mAlarmTimeTextView
+ .setText(mTimeFormat.format(alarmTime.getTime()));
+ holder.mAlarmDateTextView
+ .setText(mDateFormat.format(alarmTime.getTime()));
+ holder.mDeleteImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Alarm toBeDeleted = mAlarmList.get(position);
+ mAlarmList.removeItemAt(position);
+ mAlarmStorage.deleteAlarm(toBeDeleted);
+ mAlarmUtil.cancelAlarm(toBeDeleted);
+ notifyDataSetChanged();
+ Toast.makeText(mContext, mContext.getString(R.string.alarm_deleted,
+ toBeDeleted.hour, toBeDeleted.minute), Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return mAlarmList.size();
+ }
+
+ public void addAlarm(Alarm alarm) {
+ mAlarmList.add(alarm);
+ notifyDataSetChanged();
+ }
+
+ public void deleteAlarm(Alarm alarm) {
+ mAlarmList.remove(alarm);
+ notifyDataSetChanged();
+ }
+
+ public static class AlarmViewHolder extends RecyclerView.ViewHolder {
+
+ private TextView mAlarmTimeTextView;
+ private TextView mAlarmDateTextView;
+ private ImageView mDeleteImageView;
+
+ public AlarmViewHolder(View itemView) {
+ super(itemView);
+ mAlarmTimeTextView = (TextView) itemView.findViewById(R.id.text_alarm_time);
+ mAlarmDateTextView = (TextView) itemView.findViewById(R.id.text_alarm_date);
+ mDeleteImageView = (ImageView) itemView.findViewById(R.id.image_delete_alarm);
+ }
+ }
+
+
+ private static class SortedListCallback extends SortedList.Callback<Alarm> {
+
+ @Override
+ public int compare(Alarm o1, Alarm o2) {
+ return o1.compareTo(o2);
+ }
+
+ @Override
+ public void onInserted(int position, int count) {
+ //No op
+ }
+
+ @Override
+ public void onRemoved(int position, int count) {
+ //No op
+ }
+
+ @Override
+ public void onMoved(int fromPosition, int toPosition) {
+ //No op
+ }
+
+ @Override
+ public void onChanged(int position, int count) {
+ //No op
+ }
+
+ @Override
+ public boolean areContentsTheSame(Alarm oldItem, Alarm newItem) {
+ return oldItem.equals(newItem);
+ }
+
+ @Override
+ public boolean areItemsTheSame(Alarm item1, Alarm item2) {
+ return item1.equals(item2);
+ }
+ }
+
+ /**
+ * ItemDecoration that draws an divider between items in a RecyclerView.
+ */
+ public static class DividerItemDecoration extends RecyclerView.ItemDecoration {
+
+ private Drawable mDivider;
+
+ public DividerItemDecoration(Context context) {
+ mDivider = context.getDrawable(R.drawable.divider);
+ }
+
+ @Override
+ public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ int left = parent.getPaddingLeft();
+ int right = parent.getWidth() - parent.getPaddingRight();
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ View child = parent.getChildAt(i);
+ RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+ .getLayoutParams();
+ int top = child.getBottom() + params.bottomMargin;
+ int bottom = top + mDivider.getIntrinsicHeight();
+ mDivider.setBounds(left, top, right, bottom);
+ mDivider.draw(c);
+ }
+ }
+ }
+}
diff --git a/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/AlarmIntentService.java b/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/AlarmIntentService.java
new file mode 100644
index 0000000..446345c
--- /dev/null
+++ b/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/AlarmIntentService.java
@@ -0,0 +1,65 @@
+/*
+* Copyright 2016 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT 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.directboot.alarms;
+
+import com.example.android.directboot.R;
+
+import android.app.IntentService;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.content.LocalBroadcastManager;
+
+/**
+ * IntentService to set off an alarm.
+ */
+public class AlarmIntentService extends IntentService {
+
+ public static final String ALARM_WENT_OFF_ACTION = AlarmIntentService.class.getName()
+ + ".ALARM_WENT_OFF";
+ public static final String ALARM_KEY = "alarm_instance";
+
+ public AlarmIntentService() {
+ super(AlarmIntentService.class.getName());
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ Context context = getApplicationContext();
+ Alarm alarm = intent.getParcelableExtra(ALARM_KEY);
+
+ NotificationManager notificationManager = context
+ .getSystemService(NotificationManager.class);
+ NotificationCompat.Builder builder =
+ new NotificationCompat.Builder(context)
+ .setSmallIcon(R.drawable.ic_fbe_notification)
+ .setCategory(Notification.CATEGORY_ALARM)
+ .setSound(Settings.System.DEFAULT_ALARM_ALERT_URI)
+ .setContentTitle(context.getString(R.string.alarm_went_off, alarm.hour,
+ alarm.minute));
+ notificationManager.notify(alarm.id, builder.build());
+
+ AlarmStorage alarmStorage = new AlarmStorage(context);
+ alarmStorage.deleteAlarm(alarm);
+ Intent wentOffIntent = new Intent(ALARM_WENT_OFF_ACTION);
+ wentOffIntent.putExtra(ALARM_KEY, alarm);
+ LocalBroadcastManager.getInstance(context).sendBroadcast(wentOffIntent);
+ }
+}
diff --git a/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/AlarmStorage.java b/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/AlarmStorage.java
new file mode 100644
index 0000000..2dc0635
--- /dev/null
+++ b/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/AlarmStorage.java
@@ -0,0 +1,114 @@
+/*
+* Copyright 2016 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT 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.directboot.alarms;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.support.v4.os.BuildCompat;
+import android.util.Log;
+
+import java.security.SecureRandom;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Class responsible for saving/retrieving alarms. This class uses SharedPreferences as storage.
+ */
+public class AlarmStorage {
+
+ private static final String TAG = AlarmStorage.class.getSimpleName();
+ private static final String ALARM_PREFERENCES_NAME = "alarm_preferences";
+ private static final SecureRandom SECURE_RANDOM = new SecureRandom();
+
+ private SharedPreferences mSharedPreferences;
+
+ public AlarmStorage(Context context) {
+ Context storageContext;
+ if (BuildCompat.isAtLeastN()) {
+ // All N devices have split storage areas, but we may need to
+ // move the existing preferences to the new device protected
+ // storage area, which is where the data lives from now on.
+ final Context deviceContext = context.createDeviceProtectedStorageContext();
+ if (!deviceContext.moveSharedPreferencesFrom(context,
+ ALARM_PREFERENCES_NAME)) {
+ Log.w(TAG, "Failed to migrate shared preferences.");
+ }
+ storageContext = deviceContext;
+ } else {
+ storageContext = context;
+ }
+ mSharedPreferences = storageContext
+ .getSharedPreferences(ALARM_PREFERENCES_NAME, Context.MODE_PRIVATE);
+ }
+
+ /**
+ * Stores an alarm in the SharedPreferences.
+ *
+ * @param month the integer represents a month
+ * @param date the integer represents a date
+ * @param hour the integer as 24-hour format the alarm goes off
+ * @param minute the integer of the minute the alarm goes off
+ * @return the saved {@link Alarm} instance
+ */
+ public Alarm saveAlarm(int month, int date, int hour, int minute) {
+ Alarm alarm = new Alarm();
+ // Ignore the Id duplication if that happens
+ alarm.id = SECURE_RANDOM.nextInt();
+ alarm.month = month;
+ alarm.date = date;
+ alarm.hour = hour;
+ alarm.minute = minute;
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putString(String.valueOf(alarm.id), alarm.toJson());
+ editor.apply();
+ return alarm;
+ }
+
+ /**
+ * Retrieves the alarms stored in the SharedPreferences.
+ * This method takes linear time as the alarms count.
+ *
+ * @return a {@link Set} of alarms.
+ */
+ public Set<Alarm> getAlarms() {
+ Set<Alarm> alarms = new HashSet<>();
+ for (Map.Entry<String, ?> entry : mSharedPreferences.getAll().entrySet()) {
+ alarms.add(Alarm.fromJson(entry.getValue().toString()));
+ }
+ return alarms;
+ }
+
+ /**
+ * Delete the alarm instance passed as an argument from the SharedPreferences.
+ * This method iterates through the alarms stored in the SharedPreferences, takes linear time
+ * as the alarms count.
+ *
+ * @param toBeDeleted the alarm instance to be deleted
+ */
+ public void deleteAlarm(Alarm toBeDeleted) {
+ for (Map.Entry<String, ?> entry : mSharedPreferences.getAll().entrySet()) {
+ Alarm alarm = Alarm.fromJson(entry.getValue().toString());
+ if (alarm.id == toBeDeleted.id) {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.remove(String.valueOf(alarm.id));
+ editor.apply();
+ return;
+ }
+ }
+ }
+}
diff --git a/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/AlarmUtil.java b/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/AlarmUtil.java
new file mode 100644
index 0000000..b80e8c9
--- /dev/null
+++ b/samples/browseable/DirectBoot/src/com.example.android.directboot/alarms/AlarmUtil.java
@@ -0,0 +1,97 @@
+/*
+* Copyright 2016 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT 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.directboot.alarms;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import java.util.Calendar;
+
+/**
+ * Utility class for alarms.
+ */
+public class AlarmUtil {
+
+ private static final String TAG = "AlarmUtil";
+ private final Context mContext;
+ private final AlarmManager mAlarmManager;
+
+ public AlarmUtil(Context context) {
+ mContext = context;
+ mAlarmManager = mContext.getSystemService(AlarmManager.class);
+ }
+
+ /**
+ * Schedules an alarm using {@link AlarmManager}.
+ *
+ * @param alarm the alarm to be scheduled
+ */
+ public void scheduleAlarm(Alarm alarm) {
+ Intent intent = new Intent(mContext, AlarmIntentService.class);
+ intent.putExtra(AlarmIntentService.ALARM_KEY, alarm);
+ PendingIntent pendingIntent = PendingIntent
+ .getService(mContext, alarm.id, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ Calendar alarmTime = Calendar.getInstance();
+ alarmTime.set(Calendar.MONTH, alarm.month);
+ alarmTime.set(Calendar.DATE, alarm.date);
+ alarmTime.set(Calendar.HOUR_OF_DAY, alarm.hour);
+ alarmTime.set(Calendar.MINUTE, alarm.minute);
+
+ AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(
+ alarmTime.getTimeInMillis(),
+ pendingIntent);
+ mAlarmManager.setAlarmClock(alarmClockInfo, pendingIntent);
+ Log.i(TAG,
+ String.format("Alarm scheduled at (%2d:%02d) Date: %d, Month: %d",
+ alarm.hour, alarm.minute,
+ alarm.month, alarm.date));
+ }
+
+ /**
+ * Cancels the scheduled alarm.
+ *
+ * @param alarm the alarm to be canceled.
+ */
+ public void cancelAlarm(Alarm alarm) {
+ Intent intent = new Intent(mContext, AlarmIntentService.class);
+ intent.putExtra(AlarmIntentService.ALARM_KEY, alarm);
+ PendingIntent pendingIntent = PendingIntent
+ .getService(mContext, alarm.id, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ mAlarmManager.cancel(pendingIntent);
+ }
+
+ /**
+ * Returns a next alarm time (nearest day) Calendar instance with the hour and the minute.
+ *
+ * @param hour the integer of the hour an alarm should go off
+ * @param minute the integer of the minute an alarm should go off
+ * @return a {@link Calendar} instance an alarm should go off given the passed hour and the
+ * minute
+ */
+ public Calendar getNextAlarmTime(int hour, int minute) {
+ Calendar alarmTime = Calendar.getInstance();
+ alarmTime.set(Calendar.HOUR_OF_DAY, hour);
+ alarmTime.set(Calendar.MINUTE, minute);
+ if ((alarmTime.getTimeInMillis() - System.currentTimeMillis()) < 0) {
+ alarmTime.add(Calendar.DATE, 1);
+ }
+ return alarmTime;
+ }
+}
diff --git a/samples/browseable/FingerprintDialog/AndroidManifest.xml b/samples/browseable/FingerprintDialog/AndroidManifest.xml
index 3b19fbe..77d9949 100644
--- a/samples/browseable/FingerprintDialog/AndroidManifest.xml
+++ b/samples/browseable/FingerprintDialog/AndroidManifest.xml
@@ -22,14 +22,13 @@
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<application
- android:name=".InjectedApplication"
android:allowBackup="true"
- android:label="@string/app_name"
+ android:label="@string/application_name"
android:icon="@mipmap/ic_launcher"
- android:theme="@style/AppTheme">
+ android:theme="@style/Theme.AppCompat.Light">
<activity android:name=".MainActivity"
- android:label="@string/app_name">
+ android:label="@string/application_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
diff --git a/samples/browseable/FingerprintDialog/res/values/strings.xml b/samples/browseable/FingerprintDialog/res/values/strings.xml
index 9f5a6fd..77d1176 100644
--- a/samples/browseable/FingerprintDialog/res/values/strings.xml
+++ b/samples/browseable/FingerprintDialog/res/values/strings.xml
@@ -15,6 +15,12 @@
~ limitations under the License
-->
<resources>
+ <!--
+ The similar strings app_name is added in the Application/template directory, but depending
+ on the template directory makes it hard for the sample buildable with Android.mk.
+ Thus defining the application_name with different name from the template.
+ -->
+ <string name="application_name">FingerprintDialog</string>
<string name="action_settings">Settings</string>
<string name="cancel">Cancel</string>
<string name="use_password">Use password</string>
diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintAuthenticationDialogFragment.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintAuthenticationDialogFragment.java
index b17ebb0..cb240db 100644
--- a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintAuthenticationDialogFragment.java
+++ b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintAuthenticationDialogFragment.java
@@ -21,6 +21,7 @@
import android.content.SharedPreferences;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
+import android.preference.PreferenceManager;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -33,8 +34,6 @@
import android.widget.ImageView;
import android.widget.TextView;
-import javax.inject.Inject;
-
/**
* A dialog which uses fingerprint APIs to authenticate the user, and falls back to password
* authentication if fingerprint is not available.
@@ -57,12 +56,8 @@
private FingerprintUiHelper mFingerprintUiHelper;
private MainActivity mActivity;
- @Inject FingerprintUiHelper.FingerprintUiHelperBuilder mFingerprintUiHelperBuilder;
- @Inject InputMethodManager mInputMethodManager;
- @Inject SharedPreferences mSharedPreferences;
-
- @Inject
- public FingerprintAuthenticationDialogFragment() {}
+ private InputMethodManager mInputMethodManager;
+ private SharedPreferences mSharedPreferences;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -106,7 +101,8 @@
v.findViewById(R.id.use_fingerprint_in_future_check);
mNewFingerprintEnrolledTextView = (TextView)
v.findViewById(R.id.new_fingerprint_enrolled_description);
- mFingerprintUiHelper = mFingerprintUiHelperBuilder.build(
+ mFingerprintUiHelper = new FingerprintUiHelper(
+ mActivity.getSystemService(FingerprintManager.class),
(ImageView) v.findViewById(R.id.fingerprint_icon),
(TextView) v.findViewById(R.id.fingerprint_status), this);
updateStage();
@@ -141,6 +137,8 @@
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = (MainActivity) activity;
+ mInputMethodManager = mActivity.getSystemService(InputMethodManager.class);
+ mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mActivity);
}
/**
diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintModule.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintModule.java
deleted file mode 100644
index 964e1f6..0000000
--- a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintModule.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.example.android.fingerprintdialog;
-
-import android.app.KeyguardManager;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.hardware.fingerprint.FingerprintManager;
-import android.preference.PreferenceManager;
-import android.security.keystore.KeyProperties;
-import android.view.inputmethod.InputMethodManager;
-
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-
-import javax.crypto.Cipher;
-import javax.crypto.KeyGenerator;
-import javax.crypto.NoSuchPaddingException;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Dagger module for Fingerprint APIs.
- */
-@Module(
- library = true,
- injects = {MainActivity.class}
-)
-public class FingerprintModule {
-
- private final Context mContext;
-
- public FingerprintModule(Context context) {
- mContext = context;
- }
-
- @Provides
- public Context providesContext() {
- return mContext;
- }
-
- @Provides
- public FingerprintManager providesFingerprintManager(Context context) {
- return context.getSystemService(FingerprintManager.class);
- }
-
- @Provides
- public KeyguardManager providesKeyguardManager(Context context) {
- return context.getSystemService(KeyguardManager.class);
- }
-
- @Provides
- public KeyStore providesKeystore() {
- try {
- return KeyStore.getInstance("AndroidKeyStore");
- } catch (KeyStoreException e) {
- throw new RuntimeException("Failed to get an instance of KeyStore", e);
- }
- }
-
- @Provides
- public KeyGenerator providesKeyGenerator() {
- try {
- return KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
- } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
- throw new RuntimeException("Failed to get an instance of KeyGenerator", e);
- }
- }
-
- @Provides
- public Cipher providesCipher(KeyStore keyStore) {
- try {
- return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
- + KeyProperties.BLOCK_MODE_CBC + "/"
- + KeyProperties.ENCRYPTION_PADDING_PKCS7);
- } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
- throw new RuntimeException("Failed to get an instance of Cipher", e);
- }
- }
-
- @Provides
- public InputMethodManager providesInputMethodManager(Context context) {
- return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
- }
-
- @Provides
- public SharedPreferences providesSharedPreferences(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context);
- }
-}
diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintUiHelper.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintUiHelper.java
index 92fcdb1..be81646 100644
--- a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintUiHelper.java
+++ b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintUiHelper.java
@@ -16,22 +16,18 @@
package com.example.android.fingerprintdialog;
-import com.google.common.annotations.VisibleForTesting;
-
import android.hardware.fingerprint.FingerprintManager;
import android.os.CancellationSignal;
import android.widget.ImageView;
import android.widget.TextView;
-import javax.inject.Inject;
-
/**
* Small helper class to manage text/icon around fingerprint authentication UI.
*/
public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallback {
- @VisibleForTesting static final long ERROR_TIMEOUT_MILLIS = 1600;
- @VisibleForTesting static final long SUCCESS_DELAY_MILLIS = 1300;
+ private static final long ERROR_TIMEOUT_MILLIS = 1600;
+ private static final long SUCCESS_DELAY_MILLIS = 1300;
private final FingerprintManager mFingerprintManager;
private final ImageView mIcon;
@@ -39,31 +35,12 @@
private final Callback mCallback;
private CancellationSignal mCancellationSignal;
- @VisibleForTesting boolean mSelfCancelled;
+ private boolean mSelfCancelled;
/**
- * Builder class for {@link FingerprintUiHelper} in which injected fields from Dagger
- * holds its fields and takes other arguments in the {@link #build} method.
+ * Constructor for {@link FingerprintUiHelper}.
*/
- public static class FingerprintUiHelperBuilder {
- private final FingerprintManager mFingerPrintManager;
-
- @Inject
- public FingerprintUiHelperBuilder(FingerprintManager fingerprintManager) {
- mFingerPrintManager = fingerprintManager;
- }
-
- public FingerprintUiHelper build(ImageView icon, TextView errorTextView, Callback callback) {
- return new FingerprintUiHelper(mFingerPrintManager, icon, errorTextView,
- callback);
- }
- }
-
- /**
- * Constructor for {@link FingerprintUiHelper}. This method is expected to be called from
- * only the {@link FingerprintUiHelperBuilder} class.
- */
- private FingerprintUiHelper(FingerprintManager fingerprintManager,
+ FingerprintUiHelper(FingerprintManager fingerprintManager,
ImageView icon, TextView errorTextView, Callback callback) {
mFingerprintManager = fingerprintManager;
mIcon = icon;
@@ -72,6 +49,8 @@
}
public boolean isFingerprintAuthAvailable() {
+ // The line below prevents the false positive inspection from Android Studio
+ // noinspection ResourceType
return mFingerprintManager.isHardwareDetected()
&& mFingerprintManager.hasEnrolledFingerprints();
}
@@ -82,6 +61,8 @@
}
mCancellationSignal = new CancellationSignal();
mSelfCancelled = false;
+ // The line below prevents the false positive inspection from Android Studio
+ // noinspection ResourceType
mFingerprintManager
.authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null);
mIcon.setImageResource(R.drawable.ic_fp_40px);
@@ -144,8 +125,7 @@
mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS);
}
- @VisibleForTesting
- Runnable mResetErrorTextRunnable = new Runnable() {
+ private Runnable mResetErrorTextRunnable = new Runnable() {
@Override
public void run() {
mErrorTextView.setTextColor(
diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/InjectedApplication.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/InjectedApplication.java
deleted file mode 100644
index b7075a9..0000000
--- a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/InjectedApplication.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.example.android.fingerprintdialog;
-
-import android.app.Application;
-import android.util.Log;
-
-import dagger.ObjectGraph;
-
-/**
- * The Application class of the sample which holds the ObjectGraph in Dagger and enables
- * dependency injection.
- */
-public class InjectedApplication extends Application {
-
- private static final String TAG = InjectedApplication.class.getSimpleName();
-
- private ObjectGraph mObjectGraph;
-
- @Override
- public void onCreate() {
- super.onCreate();
-
- initObjectGraph(new FingerprintModule(this));
- }
-
- /**
- * Initialize the Dagger module. Passing null or mock modules can be used for testing.
- *
- * @param module for Dagger
- */
- public void initObjectGraph(Object module) {
- mObjectGraph = module != null ? ObjectGraph.create(module) : null;
- }
-
- public void inject(Object object) {
- if (mObjectGraph == null) {
- // This usually happens during tests.
- Log.i(TAG, "Object graph is not initialized.");
- return;
- }
- mObjectGraph.inject(object);
- }
-
-}
diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/MainActivity.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/MainActivity.java
index 7caf9e6..77c8788 100644
--- a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/MainActivity.java
+++ b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/MainActivity.java
@@ -22,6 +22,7 @@
import android.content.SharedPreferences;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
+import android.preference.PreferenceManager;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
@@ -40,6 +41,7 @@
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
@@ -47,8 +49,8 @@
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
-import javax.inject.Inject;
/**
* Main entry point for the sample, showing a backpack and "Purchase" button.
@@ -62,22 +64,40 @@
/** Alias for our key in the Android Key Store */
private static final String KEY_NAME = "my_key";
- @Inject KeyguardManager mKeyguardManager;
- @Inject FingerprintManager mFingerprintManager;
- @Inject FingerprintAuthenticationDialogFragment mFragment;
- @Inject KeyStore mKeyStore;
- @Inject KeyGenerator mKeyGenerator;
- @Inject Cipher mCipher;
- @Inject SharedPreferences mSharedPreferences;
+ private KeyStore mKeyStore;
+ private KeyGenerator mKeyGenerator;
+ private Cipher mCipher;
+ private SharedPreferences mSharedPreferences;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- ((InjectedApplication) getApplication()).inject(this);
-
setContentView(R.layout.activity_main);
+
+ try {
+ mKeyStore = KeyStore.getInstance("AndroidKeyStore");
+ } catch (KeyStoreException e) {
+ throw new RuntimeException("Failed to get an instance of KeyStore", e);
+ }
+ try {
+ mKeyGenerator = KeyGenerator
+ .getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
+ } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
+ throw new RuntimeException("Failed to get an instance of KeyGenerator", e);
+ }
+ try {
+ mCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ + KeyProperties.BLOCK_MODE_CBC + "/"
+ + KeyProperties.ENCRYPTION_PADDING_PKCS7);
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+ throw new RuntimeException("Failed to get an instance of Cipher", e);
+ }
+ mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+
+ KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
+ FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class);
Button purchaseButton = (Button) findViewById(R.id.purchase_button);
- if (!mKeyguardManager.isKeyguardSecure()) {
+ if (!keyguardManager.isKeyguardSecure()) {
// Show a message that the user hasn't set up a fingerprint or lock screen.
Toast.makeText(this,
"Secure lock screen hasn't set up.\n"
@@ -87,8 +107,11 @@
return;
}
- //noinspection ResourceType
- if (!mFingerprintManager.hasEnrolledFingerprints()) {
+ // Now the protection level of USE_FINGERPRINT permission is normal instead of dangerous.
+ // See http://developer.android.com/reference/android/Manifest.permission.html#USE_FINGERPRINT
+ // The line below prevents the false positive inspection from Android Studio
+ // noinspection ResourceType
+ if (!fingerprintManager.hasEnrolledFingerprints()) {
purchaseButton.setEnabled(false);
// This happens when no fingerprints are registered.
Toast.makeText(this,
@@ -107,30 +130,33 @@
// Set up the crypto object for later. The object will be authenticated by use
// of the fingerprint.
if (initCipher()) {
-
- // Show the fingerprint dialog. The user has the option to use the fingerprint with
- // crypto, or you can fall back to using a server-side verified password.
- mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
+ // Show the fingerprint dialog. The user has the option to use the fingerprint
+ // with crypto, or you can fall back to using a server-side verified password.
+ FingerprintAuthenticationDialogFragment fragment
+ = new FingerprintAuthenticationDialogFragment();
+ fragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
boolean useFingerprintPreference = mSharedPreferences
.getBoolean(getString(R.string.use_fingerprint_to_authenticate_key),
true);
if (useFingerprintPreference) {
- mFragment.setStage(
+ fragment.setStage(
FingerprintAuthenticationDialogFragment.Stage.FINGERPRINT);
} else {
- mFragment.setStage(
+ fragment.setStage(
FingerprintAuthenticationDialogFragment.Stage.PASSWORD);
}
- mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+ fragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
} else {
// This happens if the lock screen has been disabled or or a fingerprint got
// enrolled. Thus show the dialog to authenticate with their password first
// and ask the user if they want to authenticate with fingerprints in the
// future
- mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
- mFragment.setStage(
+ FingerprintAuthenticationDialogFragment fragment
+ = new FingerprintAuthenticationDialogFragment();
+ fragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
+ fragment.setStage(
FingerprintAuthenticationDialogFragment.Stage.NEW_FINGERPRINT_ENROLLED);
- mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+ fragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
}
}
});
@@ -210,8 +236,8 @@
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
- // Require the user to authenticate with a fingerprint to authorize every use
- // of the key
+ // Require the user to authenticate with a fingerprint to authorize every use
+ // of the key
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
diff --git a/samples/browseable/MessagingService/_index.jd b/samples/browseable/MessagingService/_index.jd
index ce82da8..63124eb 100644
--- a/samples/browseable/MessagingService/_index.jd
+++ b/samples/browseable/MessagingService/_index.jd
@@ -1,13 +1,17 @@
page.tags="MessagingService"
-sample.group=Notification
+sample.group=Android N Preview
@jd:body
<p>
This sample shows a simple service that sends notifications using
-NotificationCompat. In addition to sending a notification, it also extends
-the notification with a CarExtender to make it compatible with Android Auto.
-Each unread conversation from a user is sent as a distinct notification.
+NotificationCompat. It also extends the notification with Remote
+Input to allow Android N devices to reply via text directly from
+the notification without having to open an app. The same Remote
+Input usage also allows Android Auto users to respond by voice
+when the notifications is presented in that experience.
+Note: Each unread conversation from a user is sent as a distinct
+notification.
</p>
diff --git a/samples/browseable/MessagingService/res/values/strings.xml b/samples/browseable/MessagingService/res/values/strings.xml
index 3f8390d..31e25ec 100644
--- a/samples/browseable/MessagingService/res/values/strings.xml
+++ b/samples/browseable/MessagingService/res/values/strings.xml
@@ -16,7 +16,8 @@
-->
<resources>
<string name="app_name">Messaging Sample</string>
- <string name="notification_reply">Reply by Voice</string>
+ <string name="replied">Replied</string>
+ <string name="reply">Reply</string>
<string name="send_2_conversations">Send 2 conversations with 1 message</string>
<string name="send_1_conversation">Send 1 conversation with 1 message</string>
<string name="send_1_conv_3_messages">Send 1 conversation with 3 messages</string>
diff --git a/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessageReplyReceiver.java b/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessageReplyReceiver.java
index 0a3eba6..7a48acf 100644
--- a/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessageReplyReceiver.java
+++ b/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessageReplyReceiver.java
@@ -16,15 +16,21 @@
package com.example.android.messagingservice;
+import android.app.Notification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.graphics.BitmapFactory;
import android.os.Bundle;
+import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.app.RemoteInput;
+import android.support.v4.text.TextUtilsCompat;
+import android.support.v4.app.NotificationCompat;
+import android.text.TextUtils;
import android.util.Log;
/**
- * A receiver that gets called when a reply is sent to a given conversationId
+ * A receiver that gets called when a reply is sent to a given conversationId.
*/
public class MessageReplyReceiver extends BroadcastReceiver {
@@ -39,6 +45,17 @@
Log.d(TAG, "Got reply (" + reply + ") for ConversationId " + conversationId);
MessageLogger.logMessage(context, "ConversationId: " + conversationId +
" received a reply: [" + reply + "]");
+
+ // Update the notification to stop the progress spinner.
+ NotificationManagerCompat notificationManager =
+ NotificationManagerCompat.from(context);
+ Notification repliedNotification = new NotificationCompat.Builder(context)
+ .setSmallIcon(R.drawable.notification_icon)
+ .setLargeIcon(BitmapFactory.decodeResource(
+ context.getResources(), R.drawable.android_contact))
+ .setContentText(context.getString(R.string.replied))
+ .build();
+ notificationManager.notify(conversationId, repliedNotification);
}
}
}
@@ -51,7 +68,8 @@
private CharSequence getMessageText(Intent intent) {
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
- return remoteInput.getCharSequence(MessagingService.EXTRA_VOICE_REPLY);
+ return remoteInput.getCharSequence(
+ MessagingService.EXTRA_REMOTE_REPLY);
}
return null;
}
diff --git a/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessagingService.java b/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessagingService.java
index 73199ed..77689cd 100644
--- a/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessagingService.java
+++ b/samples/browseable/MessagingService/src/com.example.android.messagingservice/MessagingService.java
@@ -39,11 +39,10 @@
private static final String EOL = "\n";
private static final String READ_ACTION =
"com.example.android.messagingservice.ACTION_MESSAGE_READ";
-
public static final String REPLY_ACTION =
"com.example.android.messagingservice.ACTION_MESSAGE_REPLY";
public static final String CONVERSATION_ID = "conversation_id";
- public static final String EXTRA_VOICE_REPLY = "extra_voice_reply";
+ public static final String EXTRA_REMOTE_REPLY = "extra_remote_reply";
public static final int MSG_SEND_NOTIFICATION = 1;
private NotificationManagerCompat mNotificationManager;
@@ -93,9 +92,10 @@
getMessageReadIntent(conversation.getConversationId()),
PendingIntent.FLAG_UPDATE_CURRENT);
- // Build a RemoteInput for receiving voice input in a Car Notification
- RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
- .setLabel(getApplicationContext().getString(R.string.notification_reply))
+ // Build a RemoteInput for receiving voice input in a Car Notification or text input on
+ // devices that support text input (like devices on Android N and above).
+ RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_REMOTE_REPLY)
+ .setLabel(getString(R.string.reply))
.build();
// Building a Pending Intent for the reply action to trigger
@@ -104,6 +104,12 @@
getMessageReplyIntent(conversation.getConversationId()),
PendingIntent.FLAG_UPDATE_CURRENT);
+ // Build an Android N compatible Remote Input enabled action.
+ NotificationCompat.Action actionReplyByRemoteInput = new NotificationCompat.Action.Builder(
+ R.drawable.notification_icon, getString(R.string.reply), replyIntent)
+ .addRemoteInput(remoteInput)
+ .build();
+
// Create the UnreadConversation and populate it with the participant name,
// read and reply intents.
UnreadConversation.Builder unreadConvBuilder =
@@ -134,8 +140,9 @@
.setContentIntent(readPendingIntent)
.extend(new CarExtender()
.setUnreadConversation(unreadConvBuilder.build())
- .setColor(getApplicationContext()
- .getResources().getColor(R.color.default_color_light)));
+ .setColor(getApplicationContext().getResources()
+ .getColor(R.color.default_color_light)))
+ .addAction(actionReplyByRemoteInput);
MessageLogger.logMessage(getApplicationContext(), "Sending notification "
+ conversation.getConversationId() + " conversation: " + conversation);
diff --git a/samples/browseable/MultiWindowPlayground/AndroidManifest.xml b/samples/browseable/MultiWindowPlayground/AndroidManifest.xml
new file mode 100644
index 0000000..25b21bd
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/AndroidManifest.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<manifest package="com.android.multiwindowplayground"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:supportsRtl="true"
+ android:theme="@style/MultiWindowSampleTheme">
+ <!-- The launcher Activity that is started when the application is first started.
+ Note that we are setting the task affinity to "" to ensure each activity is launched
+ into a separate task stack. -->
+ <activity
+ android:name="com.android.multiwindowplayground.MainActivity"
+ android:taskAffinity="">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <!-- This Activity cannot be resized and is always displayed full screen. -->
+ <activity
+ android:name="com.android.multiwindowplayground.activities.UnresizableActivity"
+ android:resizeableActivity="false"
+ android:taskAffinity="" />
+
+ <!-- This Activity has a default size (750x500dp) with a minimum size
+ (500dp at its shortest side). It is launched in the top/end (top/right) corner by default.
+ These attributes are defined in the 'layout' tag within an Activity definition. -->
+ <activity
+ android:name="com.android.multiwindowplayground.activities.MinimumSizeActivity"
+ android:launchMode="singleInstance"
+ android:taskAffinity="">
+ <layout
+ android:defaultHeight="500dp"
+ android:defaultWidth="750dp"
+ android:gravity="top|end"
+ android:minWidth="500dp"
+ android:minHeight="500dp" />
+ </activity>
+
+ <!-- In split-screen mode, this Activity is launched adjacent to another Activity. This is
+ controlled via a flag set in the intent that launches this Activity. -->
+ <activity
+ android:name="com.android.multiwindowplayground.activities.AdjacentActivity"
+ android:taskAffinity="" />
+
+ <!-- This Activity is launched within an area defined in its launch intent. -->
+ <activity
+ android:name="com.android.multiwindowplayground.activities.LaunchBoundsActivity"
+ android:taskAffinity="" />
+
+ <!-- This activity handles all configuration changes itself.
+ Callbacks for configuration changes are received in 'onConfigurationChanged'. -->
+ <activity
+ android:name="com.android.multiwindowplayground.activities.CustomConfigurationChangeActivity"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+ android:launchMode="singleInstance"
+ android:taskAffinity="" />
+
+ <!-- This Activity is launched in a new task without any special flags or settings. -->
+ <activity
+ android:name="com.android.multiwindowplayground.activities.BasicActivity"
+ android:launchMode="singleInstance"
+ android:taskAffinity="" />
+
+ </application>
+
+</manifest>
diff --git a/samples/browseable/MultiWindowPlayground/_index.jd b/samples/browseable/MultiWindowPlayground/_index.jd
new file mode 100644
index 0000000..2839802
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/_index.jd
@@ -0,0 +1,14 @@
+
+page.tags="MultiWindowPlayground"
+sample.group=Android N Preview
+@jd:body
+
+<p>
+
+This sample demonstrates the use of the multi-window API available
+in Android N. It shows the use of new Intent flags and
+AndroidManifest properties to define the multi-window behavior.
+Switch the sample app into multi-window mode to see how it affects
+the lifecycle and behavior of the app.
+
+ </p>
diff --git a/samples/browseable/MultiWindowPlayground/res/layout/activity_logging.xml b/samples/browseable/MultiWindowPlayground/res/layout/activity_logging.xml
new file mode 100644
index 0000000..6c6d79b
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/res/layout/activity_logging.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<!-- This layout contains a TextView and a LogFragment that logs some text to the screen. -->
+<LinearLayout android:id="@+id/layout"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/white"
+ android:orientation="vertical">
+
+ <ScrollView
+ android:id="@+id/scrollview"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_gravity="top"
+ android:layout_weight="0.75">
+
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:textColor="@color/white" />
+ </ScrollView>
+
+
+ <include
+ layout="@layout/logging"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_gravity="bottom"
+ android:layout_weight="0.25" />
+</LinearLayout>
\ No newline at end of file
diff --git a/samples/browseable/MultiWindowPlayground/res/layout/activity_main.xml b/samples/browseable/MultiWindowPlayground/res/layout/activity_main.xml
new file mode 100644
index 0000000..640d9cc
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/res/layout/activity_main.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:orientation="vertical"
+ android:background="@color/lightgray">
+
+ <ScrollView xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="0.75"
+ android:background="@color/white"
+ android:layout_gravity="top"
+ android:id="@+id/scrollview"
+ tools:context="com.android.multiwindowplayground.MainActivity">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/introduction_title"
+ android:textSize="30sp" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/warning_multiwindow_disabled"
+ android:visibility="gone"
+ tools:visibility="visible"
+ style="@style/TextWarning"
+ android:paddingTop="@dimen/content_vertical_dividing_padding"
+ android:paddingBottom="@dimen/content_vertical_dividing_padding"
+ android:text="Enable multi-window mode to see this sample in action!" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/description"
+ android:text="@string/sample_introduction" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/button_start_basic"
+ android:onClick="onStartBasicActivity"
+ android:text="@string/start_default" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/start_unresizable"
+ android:onClick="onStartUnresizableClick"
+ android:text="@string/start_unresizable" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/start_adjacent"
+ android:onClick="onStartAdjacentActivity"
+ android:text="@string/start_adjacent" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/start_customconfiguration"
+ android:onClick="onStartCustomConfigurationActivity"
+ android:text="@string/start_custom_activity" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/content_vertical_dividing_padding"
+ android:text="@string/sample_freeform_introduction" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/start_minimumsize"
+ android:onClick="onStartMinimumSizeActivity"
+ android:text="@string/start_minimum" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/start_launchbounds"
+ android:onClick="onStartLaunchBoundsActivity"
+ android:text="@string/start_bounds" />
+
+ </LinearLayout>
+ </ScrollView>
+
+ <include
+ layout="@layout/logging"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_gravity="bottom"
+ android:layout_weight="0.25" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/samples/browseable/MultiWindowPlayground/res/layout/logging.xml b/samples/browseable/MultiWindowPlayground/res/layout/logging.xml
new file mode 100644
index 0000000..b2da5f1
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/res/layout/logging.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/log_fragment"
+ android:name="com.example.android.common.logger.LogFragment" />
+
diff --git a/samples/browseable/MultiWindowPlayground/res/mipmap-hdpi/ic_launcher.png b/samples/browseable/MultiWindowPlayground/res/mipmap-hdpi/ic_launcher.png
new file mode 100755
index 0000000..a150a5c
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/MultiWindowPlayground/res/mipmap-mdpi/ic_launcher.png b/samples/browseable/MultiWindowPlayground/res/mipmap-mdpi/ic_launcher.png
new file mode 100755
index 0000000..1a482dd
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/MultiWindowPlayground/res/mipmap-xhdpi/ic_launcher.png b/samples/browseable/MultiWindowPlayground/res/mipmap-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..148db37
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/MultiWindowPlayground/res/mipmap-xxhdpi/ic_launcher.png b/samples/browseable/MultiWindowPlayground/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..7c5c6a4
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/MultiWindowPlayground/res/mipmap-xxxhdpi/ic_launcher.png b/samples/browseable/MultiWindowPlayground/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100755
index 0000000..944fc54
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/MultiWindowPlayground/res/values-w820dp/dimens.xml b/samples/browseable/MultiWindowPlayground/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..e8b7e24
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/res/values-w820dp/dimens.xml
@@ -0,0 +1,19 @@
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/samples/browseable/MultiWindowPlayground/res/values/colors.xml b/samples/browseable/MultiWindowPlayground/res/values/colors.xml
new file mode 100644
index 0000000..e18e61e
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/res/values/colors.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <color name="colorPrimary">#3F51B5</color>
+ <color name="colorPrimaryDark">#303F9F</color>
+ <color name="colorAccent">#FF4081</color>
+
+ <color name="purple">#512DA8</color>
+ <color name="pink">#C2185B</color>
+ <color name="teal">#00695C</color>
+ <color name="lime">#9E9D24</color>
+ <color name="gray">#424242</color>
+ <color name="lightgray">#F5F5F5</color>
+ <color name="cyan">#00838F</color>
+
+ <color name="white">#FFFFFF</color>
+</resources>
diff --git a/samples/browseable/MultiWindowPlayground/res/values/dimens.xml b/samples/browseable/MultiWindowPlayground/res/values/dimens.xml
new file mode 100644
index 0000000..3a61036
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+ <dimen name="content_vertical_dividing_padding">16dp</dimen>
+</resources>
diff --git a/samples/browseable/MultiWindowPlayground/res/values/strings.xml b/samples/browseable/MultiWindowPlayground/res/values/strings.xml
new file mode 100644
index 0000000..430742d
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/res/values/strings.xml
@@ -0,0 +1,66 @@
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <string name="app_name">MultiWindow Playground</string>
+ <string name="introduction_title">Multiwindow Playground</string>
+ <string name="sample_introduction">This sample demonstrates the use of the multi-window API
+ available in Android N.\nFirst, switch this app into
+ <b>split-screen mode</b>
+ (for example by long-pressing the recents button). Each button below starts a new activity
+ with special flags.\n<b>See the files MainActivity.java and AndroidManifest.xml for
+ implementation details.</b>
+ </string>
+ <string name="sample_freeform_introduction">The buttons below demonstrate features only
+ available in <b>free-form multi-window mode</b>.</string>
+ <string name="start_default">Start basic, default Activity</string>
+ <string name="start_unresizable">Start unresizable Activity</string>
+ <string name="start_adjacent">Start Activity adjacent</string>
+ <string name="start_minimum">Start Activity with minimum size</string>
+ <string name="start_bounds">Start Activity with launch bounds</string>
+ <string name="start_custom_activity">Start activity that handles configuration changes.</string>
+
+ <string name="activity_description_basic">This Activity was launched in a new task without any
+ additional flags or options.
+ </string>
+ <string name="activity_description_unresizable">This activity is set as unresizable in the
+ AndroidManifest. This is done by setting the <i>resizeableActivity</i> property to
+ <i>false</i> for this activity.
+ </string>
+ <string name="activity_adjacent_description">This activity was launched with the flag
+ <b>Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT</b>.\n\nIf possible, it has been launched into the
+ adjacent area from the activity that started it.\nThis is only a hint to the system. For
+ example - if the application is not in split-screen mode, it will be launched full-screen.
+ If it is launched in the same task as the initial Activity, it will retain its activity
+ properties and its location.
+ </string>
+ <string name="activity_custom_description">This activity handles configuration changes
+ itself.\n\nIn the AndroidManifest, this activity has been configured to receive callbacks
+ for <b>screenSize|smallestScreenSize|screenLayout|orientation</b>
+ changes.\nTry resizing this activity to different sizes to see which configuration
+ properties change.
+ </string>
+ <string name="activity_bounds_description">This activity has been launched with a launch bounds
+ set in its intent. The bounds define the area into which the activity should be launched.
+ \n\nNote that this flag only applies in free-form mode.
+ </string>
+ <string name="activity_minimum_description">This activity has a minimum size.\nIt was launched
+ into the top/end corner with a a default size of 750dp by 500dp, with a minimum size of 750dp
+ as defined in its <b>layout attribute in the AndroidManifest definition</b>.
+ \n\nNote that this Activity was launched in a different task, otherwise the properties from
+ the Activity that launched this one would have been applied.
+ </string>
+</resources>
diff --git a/samples/browseable/MultiWindowPlayground/res/values/styles.xml b/samples/browseable/MultiWindowPlayground/res/values/styles.xml
new file mode 100644
index 0000000..0324dbd
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/res/values/styles.xml
@@ -0,0 +1,36 @@
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="MultiWindowSampleTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ <item name="colorPrimary">@color/colorPrimary</item>
+ <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+ <item name="colorAccent">@color/colorAccent</item>
+
+ <!-- Drawable to use in the background while the window is resizing on Android N. -->
+ <item name="android:windowBackgroundFallback">@color/colorAccent</item>
+ <item name="android:windowBackground">@color/colorAccent</item>
+ </style>
+
+ <style name="TextWarning" parent="TextAppearance.AppCompat.Medium">
+
+ </style>
+
+
+</resources>
diff --git a/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/MainActivity.java b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/MainActivity.java
new file mode 100644
index 0000000..bd7b552
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/MainActivity.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.android.multiwindowplayground;
+
+import com.android.multiwindowplayground.activities.AdjacentActivity;
+import com.android.multiwindowplayground.activities.BasicActivity;
+import com.android.multiwindowplayground.activities.CustomConfigurationChangeActivity;
+import com.android.multiwindowplayground.activities.LaunchBoundsActivity;
+import com.android.multiwindowplayground.activities.LoggingActivity;
+import com.android.multiwindowplayground.activities.MinimumSizeActivity;
+import com.android.multiwindowplayground.activities.UnresizableActivity;
+
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+
+public class MainActivity extends LoggingActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ View multiDisabledMessage = findViewById(R.id.warning_multiwindow_disabled);
+ // Display an additional message if the app is not in multiwindow mode.
+ if (!isInMultiWindowMode()) {
+ multiDisabledMessage.setVisibility(View.VISIBLE);
+ } else {
+ multiDisabledMessage.setVisibility(View.GONE);
+ }
+ }
+
+ public void onStartUnresizableClick(View view) {
+ Log.d(mLogTag, "** starting UnresizableActivity");
+
+ /*
+ * This activity is marked as 'unresizable' in the AndroidManifest. We need to specify the
+ * FLAG_ACTIVITY_NEW_TASK flag here to launch it into a new task stack, otherwise the
+ * properties from the root activity would have been inherited (which was here marked as
+ * resizable by default).
+ */
+ Intent intent = new Intent(this, UnresizableActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+
+ public void onStartMinimumSizeActivity(View view) {
+ Log.d(mLogTag, "** starting MinimumSizeActivity");
+
+ startActivity(new Intent(this, MinimumSizeActivity.class));
+ }
+
+ public void onStartAdjacentActivity(View view) {
+ Log.d(mLogTag, "** starting AdjacentActivity");
+
+ /*
+ * Start this activity adjacent to the focused activity (ie. this activity) if possible.
+ * Note that this flag is just a hint to the system and may be ignored. For example,
+ * if the activity is launched within the same task, it will be launched on top of the
+ * previous activity that started the Intent. That's why the Intent.FLAG_ACTIVITY_NEW_TASK
+ * flag is specified here in the intent - this will start the activity in a new task.
+ */
+ Intent intent = new Intent(this, AdjacentActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+
+ public void onStartLaunchBoundsActivity(View view) {
+ Log.d(mLogTag, "** starting LaunchBoundsActivity");
+
+ // Define the bounds in which the Activity will be launched into.
+ Rect bounds = new Rect(500, 300, 100, 0);
+
+ // Set the bounds as an activity option.
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchBounds(bounds);
+
+ // Start the LaunchBoundsActivity with the specified options
+ Intent intent = new Intent(this, LaunchBoundsActivity.class);
+ startActivity(intent, options.toBundle());
+
+ }
+
+ public void onStartBasicActivity(View view) {
+ Log.d(mLogTag, "** starting BasicActivity");
+
+ // Start an Activity with the default options in the 'singleTask' launch mode as defined in
+ // the AndroidManifest.xml.
+ startActivity(new Intent(this, BasicActivity.class));
+
+ }
+
+ public void onStartCustomConfigurationActivity(View view) {
+ Log.d(mLogTag, "** starting CustomConfigurationChangeActivity");
+
+ // Start an Activity that handles all configuration changes itself.
+ startActivity(new Intent(this, CustomConfigurationChangeActivity.class));
+
+ }
+}
diff --git a/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/AdjacentActivity.java b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/AdjacentActivity.java
new file mode 100644
index 0000000..cfb915b
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/AdjacentActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.android.multiwindowplayground.activities;
+
+import com.android.multiwindowplayground.R;
+
+import android.os.Bundle;
+import android.view.View;
+
+/**
+ * This Activity is to be launched adjacent to another Activity using the {@link
+ * android.content.Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT}.
+ *
+ * @see com.android.multiwindowplayground.MainActivity#onStartAdjacentActivity(View)
+ */
+public class AdjacentActivity extends LoggingActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_logging);
+
+ setBackgroundColor(R.color.teal);
+ setDescription(R.string.activity_adjacent_description);
+ }
+
+}
diff --git a/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/BasicActivity.java b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/BasicActivity.java
new file mode 100644
index 0000000..c40dee2
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/BasicActivity.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.android.multiwindowplayground.activities;
+
+import com.android.multiwindowplayground.R;
+
+import android.os.Bundle;
+import android.view.View;
+
+/**
+ * This activity is the most basic, simeple use case and is to be launched without any special
+ * flags
+ * or settings.
+ *
+ * @see com.android.multiwindowplayground.MainActivity#onStartBasicActivity(View)
+ */
+public class BasicActivity extends LoggingActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_logging);
+
+ // Set the color and description
+ setDescription(R.string.activity_description_basic);
+ setBackgroundColor(R.color.gray);
+
+ }
+}
diff --git a/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/CustomConfigurationChangeActivity.java b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/CustomConfigurationChangeActivity.java
new file mode 100644
index 0000000..32e5233
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/CustomConfigurationChangeActivity.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.android.multiwindowplayground.activities;
+
+import com.android.multiwindowplayground.R;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.View;
+
+/**
+ * This activity handles configuration changes itself. The list of configuration changes that are
+ * supported is defined in its AndroidManifest definition. Each configuration change triggers a
+ * call to {@link #onConfigurationChanged(Configuration)}, which is logged in the {@link
+ * LoggingActivity}.
+ *
+ * @see com.android.multiwindowplayground.MainActivity#onStartCustomConfigurationActivity(View)
+ */
+public class CustomConfigurationChangeActivity extends LoggingActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_logging);
+
+ setBackgroundColor(R.color.cyan);
+ setDescription(R.string.activity_custom_description);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ /*
+ Note: The implementation in LoggingActivity logs the output o the new configuration.
+ This callback is received whenever the configuration is updated, for example when the
+ size of this Activity is changed.
+ */
+ }
+}
diff --git a/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/LaunchBoundsActivity.java b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/LaunchBoundsActivity.java
new file mode 100644
index 0000000..80a98f2
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/LaunchBoundsActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.android.multiwindowplayground.activities;
+
+import com.android.multiwindowplayground.R;
+
+import android.os.Bundle;
+import android.view.View;
+
+/**
+ * In free-form mode, this activity is to be launched within a defined bounds on screen.
+ * This property is set as part of the Intent that starts this activity.
+ *
+ * @see com.android.multiwindowplayground.MainActivity#onStartLaunchBoundsActivity(View)
+ */
+public class LaunchBoundsActivity extends LoggingActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_logging);
+
+ setBackgroundColor(R.color.lime);
+ setDescription(R.string.activity_bounds_description);
+ }
+
+}
diff --git a/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/LoggingActivity.java b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/LoggingActivity.java
new file mode 100644
index 0000000..988a319
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/LoggingActivity.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.android.multiwindowplayground.activities;
+
+import com.android.multiwindowplayground.R;
+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 android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.support.annotation.ColorRes;
+import android.support.annotation.StringRes;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * Activity that logs all key lifecycle callbacks to {@link Log}.
+ * Output is also logged to the UI into a {@link LogFragment} through {@link #initializeLogging()}
+ * and {@link #stopLogging()}.
+ */
+public abstract class LoggingActivity extends AppCompatActivity {
+
+ protected String mLogTag = getClass().getSimpleName();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.d(mLogTag, "onCreate");
+
+
+ }
+
+ @Override
+ public void onPostCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
+ super.onPostCreate(savedInstanceState, persistentState);
+ Log.d(mLogTag, "onPostCreate");
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ Log.d(mLogTag, "onPause");
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ Log.d(mLogTag, "onDestroy");
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Log.d(mLogTag, "onResume");
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ Log.d(mLogTag, "onConfigurationChanged: " + newConfig.toString());
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ Log.d(mLogTag, "onPostCreate");
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ // Start logging to UI.
+ initializeLogging();
+
+ Log.d(mLogTag, "onStart");
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ // Stop logging to UI when this activity is stopped.
+ stopLogging();
+
+ Log.d(mLogTag, "onStop");
+ }
+
+ @Override
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ super.onMultiWindowModeChanged(isInMultiWindowMode);
+
+ Log.d(mLogTag, "onMultiWindowModeChanged: " + isInMultiWindowMode);
+ }
+
+ // Logging and UI methods below.
+
+ /** 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);
+
+ // 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 stopLogging() {
+ Log.setLogNode(null);
+ }
+
+ /**
+ * Set the description text if a TextView with the id <code>description</code> is available.
+ */
+ protected void setDescription(@StringRes int textId) {
+ // Set the text and background color
+ TextView description = (TextView) findViewById(R.id.description);
+ if (description != null) {
+ description.setText(textId);
+ }
+ }
+
+ /**
+ * Set the background color for the description text.
+ */
+ protected void setBackgroundColor(@ColorRes int colorId) {
+ View scrollView = findViewById(R.id.scrollview);
+ if (scrollView != null) {
+ scrollView.setBackgroundResource(colorId);
+ }
+ }
+
+}
diff --git a/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/MinimumSizeActivity.java b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/MinimumSizeActivity.java
new file mode 100644
index 0000000..a42c379
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/MinimumSizeActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.android.multiwindowplayground.activities;
+
+import com.android.multiwindowplayground.R;
+
+import android.os.Bundle;
+import android.view.View;
+
+/**
+ * This Activity has a minimum size defined in the AndroidManifeset.
+ *
+ * @see com.android.multiwindowplayground.MainActivity#onStartMinimumSizeActivity(View)
+ */
+public class MinimumSizeActivity extends LoggingActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_logging);
+
+ setBackgroundColor(R.color.pink);
+ setDescription(R.string.activity_minimum_description);
+ }
+
+}
diff --git a/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/UnresizableActivity.java b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/UnresizableActivity.java
new file mode 100644
index 0000000..eb2523e
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/src/com/android.multiwindowplayground/activities/UnresizableActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.android.multiwindowplayground.activities;
+
+import com.android.multiwindowplayground.R;
+
+import android.os.Bundle;
+import android.view.View;
+
+/**
+ * This Activity is defined as unresizable in the AndroidManifest.
+ * This means that this activity is always launched full screen and will not be resized by the
+ * system.
+ *
+ * @see com.android.multiwindowplayground.MainActivity#onStartUnresizableClick(View)
+ */
+public class UnresizableActivity extends LoggingActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_logging);
+
+ setBackgroundColor(R.color.purple);
+ setDescription(R.string.activity_description_unresizable);
+ }
+
+}
diff --git a/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/Log.java b/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/Log.java
new file mode 100755
index 0000000..7c11223
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/Log.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.common.logger;
+
+/**
+ * Helper class for a list (or tree) of LoggerNodes.
+ *
+ * <p>When this is set as the head of the list,
+ * an instance of it can function as a drop-in replacement for {@link android.util.Log}.
+ * Most of the methods in this class server only to map a method call in Log to its equivalent
+ * in LogNode.</p>
+ */
+public class Log {
+
+ // Grabbing the native values from Android's native logging facilities,
+ // to make for easy migration and interop.
+ public static final int NONE = -1;
+ public static final int VERBOSE = android.util.Log.VERBOSE;
+ public static final int DEBUG = android.util.Log.DEBUG;
+ public static final int INFO = android.util.Log.INFO;
+ public static final int WARN = android.util.Log.WARN;
+ public static final int ERROR = android.util.Log.ERROR;
+ public static final int ASSERT = android.util.Log.ASSERT;
+
+ // Stores the beginning of the LogNode topology.
+ private static LogNode mLogNode;
+
+ /**
+ * Returns the next LogNode in the linked list.
+ */
+ public static LogNode getLogNode() {
+ return mLogNode;
+ }
+
+ /**
+ * Sets the LogNode data will be sent to.
+ */
+ public static void setLogNode(LogNode node) {
+ mLogNode = node;
+ }
+
+ /**
+ * Instructs the LogNode to print the log data provided. Other LogNodes can
+ * be chained to the end of the LogNode as desired.
+ *
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging
+ * facilities
+ * to extract and print useful information.
+ */
+ public static void println(int priority, String tag, String msg, Throwable tr) {
+ if (mLogNode != null) {
+ mLogNode.println(priority, tag, msg, tr);
+ }
+ }
+
+ /**
+ * Instructs the LogNode to print the log data provided. Other LogNodes can
+ * be chained to the end of the LogNode as desired.
+ *
+ * @param priority Log level of the data being logged. Verbose, Error, etc.
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged. The actual message to be logged.
+ */
+ public static void println(int priority, String tag, String msg) {
+ println(priority, tag, msg, null);
+ }
+
+ /**
+ * Prints a message at VERBOSE priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void v(String tag, String msg, Throwable tr) {
+ println(VERBOSE, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at VERBOSE priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void v(String tag, String msg) {
+ v(tag, msg, null);
+ }
+
+
+ /**
+ * Prints a message at DEBUG priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void d(String tag, String msg, Throwable tr) {
+ println(DEBUG, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at DEBUG priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void d(String tag, String msg) {
+ d(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at INFO priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void i(String tag, String msg, Throwable tr) {
+ println(INFO, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at INFO priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void i(String tag, String msg) {
+ i(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void w(String tag, String msg, Throwable tr) {
+ println(WARN, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void w(String tag, String msg) {
+ w(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at WARN priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void w(String tag, Throwable tr) {
+ w(tag, null, tr);
+ }
+
+ /**
+ * Prints a message at ERROR priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void e(String tag, String msg, Throwable tr) {
+ println(ERROR, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at ERROR priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void e(String tag, String msg) {
+ e(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void wtf(String tag, String msg, Throwable tr) {
+ println(ASSERT, tag, msg, tr);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param msg The actual message to be logged.
+ */
+ public static void wtf(String tag, String msg) {
+ wtf(tag, msg, null);
+ }
+
+ /**
+ * Prints a message at ASSERT priority.
+ *
+ * @param tag Tag for for the log data. Can be used to organize log statements.
+ * @param tr If an exception was thrown, this can be sent along for the logging facilities
+ * to extract and print useful information.
+ */
+ public static void wtf(String tag, Throwable tr) {
+ wtf(tag, null, tr);
+ }
+}
diff --git a/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/LogFragment.java b/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/LogFragment.java
new file mode 100755
index 0000000..0e9ea9e
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/LogFragment.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.setTextAppearance(android.R.style.TextAppearance_Material_Medium);
+ mLogView.setLayoutParams(logParams);
+ mLogView.setClickable(true);
+ mLogView.setFocusable(true);
+ mLogView.setTypeface(Typeface.create("monospace", Typeface.NORMAL));
+
+ // 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);
+
+ 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.post(new Runnable() {
+ @Override
+ public void run() {
+ mScrollView
+ .smoothScrollTo(0, mScrollView.getBottom() + mLogView.getHeight());
+ }
+ });
+ }
+ });
+ return result;
+ }
+
+ public LogView getLogView() {
+ return mLogView;
+ }
+}
\ No newline at end of file
diff --git a/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/LogNode.java b/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/LogNode.java
new file mode 100755
index 0000000..7620ca6
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/LogNode.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/MultiWindowPlayground/src/com/example.android.common.logger/LogView.java b/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/LogView.java
new file mode 100755
index 0000000..b1fb205
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/LogView.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.AttributeSet;
+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/MultiWindowPlayground/src/com/example.android.common.logger/LogWrapper.java b/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/LogWrapper.java
new file mode 100755
index 0000000..8594bc4
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/LogWrapper.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/MultiWindowPlayground/src/com/example.android.common.logger/MessageOnlyLogFilter.java b/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/MessageOnlyLogFilter.java
new file mode 100755
index 0000000..b86e1ae
--- /dev/null
+++ b/samples/browseable/MultiWindowPlayground/src/com/example.android.common.logger/MessageOnlyLogFilter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/NfcProvisioning/src/com.example.android.nfcprovisioning/ProvisioningValuesLoader.java b/samples/browseable/NfcProvisioning/src/com.example.android.nfcprovisioning/ProvisioningValuesLoader.java
index 2203c30..73a6053 100644
--- a/samples/browseable/NfcProvisioning/src/com.example.android.nfcprovisioning/ProvisioningValuesLoader.java
+++ b/samples/browseable/NfcProvisioning/src/com.example.android.nfcprovisioning/ProvisioningValuesLoader.java
@@ -30,8 +30,12 @@
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
+import java.io.StringWriter;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
import java.util.TimeZone;
/**
@@ -55,6 +59,7 @@
public Map<String, String> loadInBackground() {
HashMap<String, String> values = new HashMap<>();
loadFromDisk(values);
+ gatherAdminExtras(values);
loadSystemValues(values);
return values;
}
@@ -130,6 +135,29 @@
}
}
+ private void gatherAdminExtras(HashMap<String, String> values) {
+ HashMap<String, String> newMap = new HashMap<String, String>();
+ Properties props = new Properties();
+ Set<String>keys = new HashSet(values.keySet());
+ for (String key : keys) {
+ if (key.startsWith("android.app.extra")) {
+ continue;
+ }
+ props.put(key, values.get(key));
+ values.remove(key);
+ }
+ StringWriter sw = new StringWriter();
+ try{
+ props.store(sw, "admin extras bundle");
+ values.put(DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE,
+ sw.toString());
+ Log.d(TAG, "Admin extras bundle=" + values.get(
+ DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE));
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to build admin extras bundle");
+ }
+ }
+
private void loadSystemValues(HashMap<String, String> values) {
Context context = getContext();
putIfMissing(values, DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
diff --git a/samples/browseable/RecyclerView/src/com.example.android.recyclerview/CustomAdapter.java b/samples/browseable/RecyclerView/src/com.example.android.recyclerview/CustomAdapter.java
index 40f9375..de704ea 100644
--- a/samples/browseable/RecyclerView/src/com.example.android.recyclerview/CustomAdapter.java
+++ b/samples/browseable/RecyclerView/src/com.example.android.recyclerview/CustomAdapter.java
@@ -45,7 +45,7 @@
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- Log.d(TAG, "Element " + getPosition() + " clicked.");
+ Log.d(TAG, "Element " + getAdapterPosition() + " clicked.");
}
});
textView = (TextView) v.findViewById(R.id.textView);
diff --git a/samples/browseable/ScopedDirectoryAccess/AndroidManifest.xml b/samples/browseable/ScopedDirectoryAccess/AndroidManifest.xml
new file mode 100644
index 0000000..6a189d6
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?><!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.scopeddirectoryaccess"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <application
+ android:allowBackup="true"
+ android:label="@string/app_name"
+ android:icon="@mipmap/ic_launcher"
+ android:theme="@style/Theme.AppCompat.Light">
+
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/samples/browseable/ScopedDirectoryAccess/_index.jd b/samples/browseable/ScopedDirectoryAccess/_index.jd
new file mode 100644
index 0000000..eff2767
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/_index.jd
@@ -0,0 +1,12 @@
+
+page.tags="ScopedDirectoryAccess"
+sample.group=Android N Preview
+@jd:body
+
+<p>
+
+This sample demonstrates how to use the Scoped Directory Access API introduced in Android N
+to easily access to specific directories such as Pictures, Downloads instead of requesting
+READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE in your manifest.
+
+ </p>
diff --git a/samples/browseable/ScopedDirectoryAccess/res/drawable-hdpi/ic_description_grey600_36dp.png b/samples/browseable/ScopedDirectoryAccess/res/drawable-hdpi/ic_description_grey600_36dp.png
new file mode 100644
index 0000000..1524fca
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/drawable-hdpi/ic_description_grey600_36dp.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/drawable-hdpi/ic_directory_grey600_36dp.png b/samples/browseable/ScopedDirectoryAccess/res/drawable-hdpi/ic_directory_grey600_36dp.png
new file mode 100644
index 0000000..f9403da
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/drawable-hdpi/ic_directory_grey600_36dp.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/drawable-hdpi/tile.9.png b/samples/browseable/ScopedDirectoryAccess/res/drawable-hdpi/tile.9.png
new file mode 100644
index 0000000..1358628
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/drawable-hdpi/tile.9.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/drawable-mdpi/ic_description_grey600_36dp.png b/samples/browseable/ScopedDirectoryAccess/res/drawable-mdpi/ic_description_grey600_36dp.png
new file mode 100644
index 0000000..eca3020
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/drawable-mdpi/ic_description_grey600_36dp.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/drawable-mdpi/ic_directory_grey600_36dp.png b/samples/browseable/ScopedDirectoryAccess/res/drawable-mdpi/ic_directory_grey600_36dp.png
new file mode 100644
index 0000000..dd0d913
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/drawable-mdpi/ic_directory_grey600_36dp.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/drawable-xhdpi/ic_description_grey600_36dp.png b/samples/browseable/ScopedDirectoryAccess/res/drawable-xhdpi/ic_description_grey600_36dp.png
new file mode 100644
index 0000000..d5c37f2
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/drawable-xhdpi/ic_description_grey600_36dp.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/drawable-xhdpi/ic_directory_grey600_36dp.png b/samples/browseable/ScopedDirectoryAccess/res/drawable-xhdpi/ic_directory_grey600_36dp.png
new file mode 100644
index 0000000..e80139d
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/drawable-xhdpi/ic_directory_grey600_36dp.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/drawable-xxhdpi/ic_description_grey600_36dp.png b/samples/browseable/ScopedDirectoryAccess/res/drawable-xxhdpi/ic_description_grey600_36dp.png
new file mode 100644
index 0000000..fc765f0
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/drawable-xxhdpi/ic_description_grey600_36dp.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/drawable-xxhdpi/ic_directory_grey600_36dp.png b/samples/browseable/ScopedDirectoryAccess/res/drawable-xxhdpi/ic_directory_grey600_36dp.png
new file mode 100644
index 0000000..240060b
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/drawable-xxhdpi/ic_directory_grey600_36dp.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/drawable-xxxhdpi/ic_description_grey600_36dp.png b/samples/browseable/ScopedDirectoryAccess/res/drawable-xxxhdpi/ic_description_grey600_36dp.png
new file mode 100644
index 0000000..c3e6591
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/drawable-xxxhdpi/ic_description_grey600_36dp.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/drawable-xxxhdpi/ic_directory_grey600_36dp.png b/samples/browseable/ScopedDirectoryAccess/res/drawable-xxxhdpi/ic_directory_grey600_36dp.png
new file mode 100644
index 0000000..e1c2f0a
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/drawable-xxxhdpi/ic_directory_grey600_36dp.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/layout/activity_main.xml b/samples/browseable/ScopedDirectoryAccess/res/layout/activity_main.xml
new file mode 100755
index 0000000..e77f459
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/layout/activity_main.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="com.example.android.scopeddirectoryaccess.MainActivity"
+ tools:ignore="MergeRootFrame" />
diff --git a/samples/browseable/ScopedDirectoryAccess/res/layout/directory_entry.xml b/samples/browseable/ScopedDirectoryAccess/res/layout/directory_entry.xml
new file mode 100644
index 0000000..340fdf1
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/layout/directory_entry.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/directory_item_height"
+ android:orientation="horizontal"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical">
+
+ <ImageView
+ android:id="@+id/imageview_entry"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_directory_grey600_36dp" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/margin_medium"
+ android:orientation="vertical">
+
+ <View
+ android:id="@+id/divisor"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="#aaaaaa" />
+
+ <TextView
+ android:id="@+id/textview_filename"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="#000000" />
+
+ <TextView
+ android:id="@+id/textview_mimetype"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+</LinearLayout>
diff --git a/samples/browseable/ScopedDirectoryAccess/res/layout/fragment_scoped_directory_access.xml b/samples/browseable/ScopedDirectoryAccess/res/layout/fragment_scoped_directory_access.xml
new file mode 100644
index 0000000..dfee2aa
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/layout/fragment_scoped_directory_access.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:gravity="center_vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="@dimen/margin_medium">
+
+ <LinearLayout
+ android:id="@+id/container_volumes"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ >
+
+ <LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginBottom="@dimen/margin_small">
+
+ <TextView
+ android:id="@+id/textview_primary_volume_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/margin_medium"
+ android:text="@string/primary_volume_description"
+ />
+
+ <Button
+ android:id="@+id/button_open_directory_primary_volume"
+ android:text="@string/open_directory"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <Spinner
+ android:id="@+id/spinner_directories"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/margin_small" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginStart="@dimen/margin_small"
+ android:layout_marginEnd="@dimen/margin_small">
+
+ <TextView
+ android:id="@+id/label_current_directory"
+ android:text="@string/selected_directory"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/textview_current_directory"
+ android:enabled="false"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="#000000" />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/textview_nothing_in_directory"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/margin_small"
+ android:layout_marginTop="@dimen/margin_medium"
+ android:text="@string/nothing_in_directory"
+ android:visibility="gone" />
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/recyclerview_directory_entries"
+ android:layout_marginStart="@dimen/margin_small"
+ android:layout_marginEnd="@dimen/margin_small"
+ android:scrollbars="vertical"
+ android:drawSelectorOnTop="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layoutManager="LinearLayoutManager" />
+
+</LinearLayout>
+
diff --git a/samples/browseable/ScopedDirectoryAccess/res/layout/volume_entry.xml b/samples/browseable/ScopedDirectoryAccess/res/layout/volume_entry.xml
new file mode 100644
index 0000000..1686220
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/layout/volume_entry.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginBottom="@dimen/margin_small">
+
+ <TextView
+ android:id="@+id/textview_volume_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/margin_medium"
+ />
+
+ <Button
+ android:id="@+id/button_open_directory"
+ android:text="@string/open_directory"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+</LinearLayout>
\ No newline at end of file
diff --git a/samples/browseable/ScopedDirectoryAccess/res/mipmap-hdpi/ic_launcher.png b/samples/browseable/ScopedDirectoryAccess/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..f3a35bf
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/mipmap-mdpi/ic_launcher.png b/samples/browseable/ScopedDirectoryAccess/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c77f7e8
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/mipmap-xhdpi/ic_launcher.png b/samples/browseable/ScopedDirectoryAccess/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..7a328e0
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/mipmap-xxhdpi/ic_launcher.png b/samples/browseable/ScopedDirectoryAccess/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..59a1bf7
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/mipmap-xxxhdpi/ic_launcher.png b/samples/browseable/ScopedDirectoryAccess/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b35ede1
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/browseable/ScopedDirectoryAccess/res/values-sw600dp/template-dimens.xml b/samples/browseable/ScopedDirectoryAccess/res/values-sw600dp/template-dimens.xml
new file mode 100644
index 0000000..22074a2
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/values-sw600dp/template-dimens.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <!-- Semantic definitions -->
+
+ <dimen name="horizontal_page_margin">@dimen/margin_huge</dimen>
+ <dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
+
+</resources>
diff --git a/samples/browseable/ScopedDirectoryAccess/res/values-sw600dp/template-styles.xml b/samples/browseable/ScopedDirectoryAccess/res/values-sw600dp/template-styles.xml
new file mode 100644
index 0000000..03d1974
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/values-sw600dp/template-styles.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <style name="Widget.SampleMessage">
+ <item name="android:textAppearance">?android:textAppearanceLarge</item>
+ <item name="android:lineSpacingMultiplier">1.2</item>
+ <item name="android:shadowDy">-6.5</item>
+ </style>
+
+</resources>
diff --git a/samples/browseable/ScopedDirectoryAccess/res/values-v11/template-styles.xml b/samples/browseable/ScopedDirectoryAccess/res/values-v11/template-styles.xml
new file mode 100644
index 0000000..8c1ea66
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/values-v11/template-styles.xml
@@ -0,0 +1,22 @@
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <!-- Activity themes -->
+ <style name="Theme.Base" parent="android:Theme.Holo.Light" />
+
+</resources>
diff --git a/samples/browseable/ScopedDirectoryAccess/res/values-v21/base-colors.xml b/samples/browseable/ScopedDirectoryAccess/res/values-v21/base-colors.xml
new file mode 100644
index 0000000..8b6ec3f
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/values-v21/base-colors.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+
+
+</resources>
diff --git a/samples/browseable/ScopedDirectoryAccess/res/values-v21/base-template-styles.xml b/samples/browseable/ScopedDirectoryAccess/res/values-v21/base-template-styles.xml
new file mode 100644
index 0000000..c778e4f
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/values-v21/base-template-styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+
+ <!-- Activity themes -->
+ <style name="Theme.Base" parent="android:Theme.Material.Light">
+ </style>
+
+</resources>
diff --git a/samples/browseable/ScopedDirectoryAccess/res/values/base-strings.xml b/samples/browseable/ScopedDirectoryAccess/res/values/base-strings.xml
new file mode 100644
index 0000000..ae1ec73
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/values/base-strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="app_name">ScopedDirectoryAccess</string>
+ <string name="intro_message">
+ <![CDATA[
+
+
+This sample demonstrates how to use the Scoped Directory Access API introduced in Android N
+to easily access to specific directories such as Pictures, Downloads instead of requesting
+READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE in your manifest.
+
+
+ ]]>
+ </string>
+</resources>
diff --git a/samples/browseable/ScopedDirectoryAccess/res/values/dimens.xml b/samples/browseable/ScopedDirectoryAccess/res/values/dimens.xml
new file mode 100644
index 0000000..82b5dd2
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/values/dimens.xml
@@ -0,0 +1,18 @@
+<!--
+ Copyright 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+ <dimen name="directory_item_height">72dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/samples/browseable/ScopedDirectoryAccess/res/values/strings.xml b/samples/browseable/ScopedDirectoryAccess/res/values/strings.xml
new file mode 100644
index 0000000..8c4edcd
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/values/strings.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="open_directory">Open</string>
+ <string name="selected_directory">Selected directory:\ </string>
+ <string name="nothing_in_directory">Nothing in the directory</string>
+ <string name="primary_volume_description">Internal shared storage</string>
+ <string-array name="directories">
+ <item>ALARMS</item>
+ <item>DCIM</item>
+ <item>DOCUMENTS</item>
+ <item>DOWNLOADS</item>
+ <item>MOVIES</item>
+ <item>MUSIC</item>
+ <item>NOTIFICATIONS</item>
+ <item>PICTURES</item>
+ <item>PODCASTS</item>
+ <item>RINGTONES</item>
+ </string-array>
+</resources>
\ No newline at end of file
diff --git a/samples/browseable/ScopedDirectoryAccess/res/values/template-dimens.xml b/samples/browseable/ScopedDirectoryAccess/res/values/template-dimens.xml
new file mode 100644
index 0000000..39e710b
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/values/template-dimens.xml
@@ -0,0 +1,32 @@
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <!-- Define standard dimensions to comply with Holo-style grids and rhythm. -->
+
+ <dimen name="margin_tiny">4dp</dimen>
+ <dimen name="margin_small">8dp</dimen>
+ <dimen name="margin_medium">16dp</dimen>
+ <dimen name="margin_large">32dp</dimen>
+ <dimen name="margin_huge">64dp</dimen>
+
+ <!-- Semantic definitions -->
+
+ <dimen name="horizontal_page_margin">@dimen/margin_medium</dimen>
+ <dimen name="vertical_page_margin">@dimen/margin_medium</dimen>
+
+</resources>
diff --git a/samples/browseable/ScopedDirectoryAccess/res/values/template-styles.xml b/samples/browseable/ScopedDirectoryAccess/res/values/template-styles.xml
new file mode 100644
index 0000000..6e7d593
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/res/values/template-styles.xml
@@ -0,0 +1,42 @@
+<!--
+ Copyright 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <!-- Activity themes -->
+
+ <style name="Theme.Base" parent="android:Theme.Light" />
+
+ <style name="Theme.Sample" parent="Theme.Base" />
+
+ <style name="AppTheme" parent="Theme.Sample" />
+ <!-- Widget styling -->
+
+ <style name="Widget" />
+
+ <style name="Widget.SampleMessage">
+ <item name="android:textAppearance">?android:textAppearanceMedium</item>
+ <item name="android:lineSpacingMultiplier">1.1</item>
+ </style>
+
+ <style name="Widget.SampleMessageTile">
+ <item name="android:background">@drawable/tile</item>
+ <item name="android:shadowColor">#7F000000</item>
+ <item name="android:shadowDy">-3.5</item>
+ <item name="android:shadowRadius">2</item>
+ </style>
+
+</resources>
diff --git a/samples/browseable/ScopedDirectoryAccess/src/com.example.android.scopeddirectoryaccess/DirectoryEntry.java b/samples/browseable/ScopedDirectoryAccess/src/com.example.android.scopeddirectoryaccess/DirectoryEntry.java
new file mode 100644
index 0000000..0eb06da
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/src/com.example.android.scopeddirectoryaccess/DirectoryEntry.java
@@ -0,0 +1,59 @@
+/*
+* Copyright (C) 2014 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.example.android.scopeddirectoryaccess;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Entity class that represents a directory entry.
+ */
+public class DirectoryEntry implements Parcelable {
+ public String fileName;
+ public String mimeType;
+
+ public DirectoryEntry() {}
+
+ protected DirectoryEntry(Parcel in) {
+ fileName = in.readString();
+ mimeType = in.readString();
+ }
+
+ public static final Creator<DirectoryEntry> CREATOR = new Creator<DirectoryEntry>() {
+ @Override
+ public DirectoryEntry createFromParcel(Parcel in) {
+ return new DirectoryEntry(in);
+ }
+
+ @Override
+ public DirectoryEntry[] newArray(int size) {
+ return new DirectoryEntry[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ parcel.writeString(fileName);
+ parcel.writeString(mimeType);
+ }
+}
+
diff --git a/samples/browseable/ScopedDirectoryAccess/src/com.example.android.scopeddirectoryaccess/DirectoryEntryAdapter.java b/samples/browseable/ScopedDirectoryAccess/src/com.example.android.scopeddirectoryaccess/DirectoryEntryAdapter.java
new file mode 100644
index 0000000..3d9e43a
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/src/com.example.android.scopeddirectoryaccess/DirectoryEntryAdapter.java
@@ -0,0 +1,75 @@
+package com.example.android.scopeddirectoryaccess;
+
+import android.provider.DocumentsContract;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provide views to RecyclerView with the directory entries.
+ */
+public class DirectoryEntryAdapter extends RecyclerView.Adapter<DirectoryEntryAdapter.ViewHolder> {
+
+ private List<DirectoryEntry> mDirectoryEntries;
+
+ public DirectoryEntryAdapter() {
+ this(new ArrayList<DirectoryEntry>());
+ }
+
+ public DirectoryEntryAdapter(List<DirectoryEntry> directoryEntries) {
+ mDirectoryEntries = directoryEntries;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
+ View v = LayoutInflater.from(viewGroup.getContext())
+ .inflate(R.layout.directory_entry, viewGroup, false);
+ return new ViewHolder(v);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder viewHolder, final int position) {
+ viewHolder.fileName.setText(mDirectoryEntries.get(position).fileName);
+ viewHolder.mimeType.setText(mDirectoryEntries.get(position).mimeType);
+
+ if (DocumentsContract.Document.MIME_TYPE_DIR
+ .equals(mDirectoryEntries.get(position).mimeType)) {
+ viewHolder.imageView.setImageResource(R.drawable.ic_directory_grey600_36dp);
+ } else {
+ viewHolder.imageView.setImageResource(R.drawable.ic_description_grey600_36dp);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mDirectoryEntries.size();
+ }
+
+ public void setDirectoryEntries(List<DirectoryEntry> directoryEntries) {
+ mDirectoryEntries = directoryEntries;
+ }
+
+ /**
+ * Provide a reference to the type of views that you are using (custom ViewHolder)
+ */
+ public class ViewHolder extends RecyclerView.ViewHolder {
+
+ public TextView fileName;
+ public TextView mimeType;
+ public ImageView imageView;
+
+ public ViewHolder(View v) {
+ super(v);
+ fileName = (TextView) v.findViewById(R.id.textview_filename);
+ mimeType = (TextView) v.findViewById(R.id.textview_mimetype);
+ imageView = (ImageView) v.findViewById(R.id.imageview_entry);
+ }
+ }
+}
+
diff --git a/samples/browseable/ScopedDirectoryAccess/src/com.example.android.scopeddirectoryaccess/MainActivity.java b/samples/browseable/ScopedDirectoryAccess/src/com.example.android.scopeddirectoryaccess/MainActivity.java
new file mode 100644
index 0000000..a80a283
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/src/com.example.android.scopeddirectoryaccess/MainActivity.java
@@ -0,0 +1,40 @@
+/*
+* Copyright 2016 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT 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.scopeddirectoryaccess;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+
+/**
+ * Launcher Activity for the Scoped Directory Access API sample app.
+ */
+public class MainActivity extends AppCompatActivity {
+
+ private static final String FRAGMENT_TAG = ScopedDirectoryAccessFragment.class.getSimpleName();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ if (savedInstanceState == null) {
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.container, ScopedDirectoryAccessFragment.newInstance(), FRAGMENT_TAG)
+ .commit();
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/browseable/ScopedDirectoryAccess/src/com.example.android.scopeddirectoryaccess/ScopedDirectoryAccessFragment.java b/samples/browseable/ScopedDirectoryAccess/src/com.example.android.scopeddirectoryaccess/ScopedDirectoryAccessFragment.java
new file mode 100644
index 0000000..23f1bb3
--- /dev/null
+++ b/samples/browseable/ScopedDirectoryAccess/src/com.example.android.scopeddirectoryaccess/ScopedDirectoryAccessFragment.java
@@ -0,0 +1,248 @@
+/*
+* Copyright 2016 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT 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.scopeddirectoryaccess;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.provider.DocumentsContract;
+import android.support.v4.app.Fragment;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Fragment that displays the directory contents.
+ */
+public class ScopedDirectoryAccessFragment extends Fragment {
+
+ private static final String DIRECTORY_ENTRIES_KEY = "directory_entries";
+ private static final String SELECTED_DIRECTORY_KEY = "selected_directory";
+ private static final int OPEN_DIRECTORY_REQUEST_CODE = 1;
+
+ private static final String[] DIRECTORY_SELECTION = new String[]{
+ DocumentsContract.Document.COLUMN_DISPLAY_NAME,
+ DocumentsContract.Document.COLUMN_MIME_TYPE,
+ DocumentsContract.Document.COLUMN_DOCUMENT_ID,
+ };
+
+ private Activity mActivity;
+ private StorageManager mStorageManager;
+ private TextView mCurrentDirectoryTextView;
+ private TextView mNothingInDirectoryTextView;
+ private TextView mPrimaryVolumeNameTextView;
+ private Spinner mDirectoriesSpinner;
+ private DirectoryEntryAdapter mAdapter;
+ private ArrayList<DirectoryEntry> mDirectoryEntries;
+
+ public static ScopedDirectoryAccessFragment newInstance() {
+ ScopedDirectoryAccessFragment fragment = new ScopedDirectoryAccessFragment();
+ return fragment;
+ }
+
+ public ScopedDirectoryAccessFragment() {
+ // Required empty public constructor
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mActivity = getActivity();
+ mStorageManager = mActivity.getSystemService(StorageManager.class);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == OPEN_DIRECTORY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+ getActivity().getContentResolver().takePersistableUriPermission(data.getData(),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ updateDirectoryEntries(data.getData());
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_scoped_directory_access, container, false);
+ }
+
+ @Override
+ public void onViewCreated(final View rootView, Bundle savedInstanceState) {
+ super.onViewCreated(rootView, savedInstanceState);
+
+ mCurrentDirectoryTextView = (TextView) rootView
+ .findViewById(R.id.textview_current_directory);
+ mNothingInDirectoryTextView = (TextView) rootView
+ .findViewById(R.id.textview_nothing_in_directory);
+ mPrimaryVolumeNameTextView = (TextView) rootView
+ .findViewById(R.id.textview_primary_volume_name);
+
+ // Set onClickListener for the primary volume
+ Button openPictureButton = (Button) rootView
+ .findViewById(R.id.button_open_directory_primary_volume);
+ openPictureButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ String selected = mDirectoriesSpinner.getSelectedItem().toString();
+ String directoryName = getDirectoryName(selected);
+ StorageVolume storageVolume = mStorageManager.getPrimaryStorageVolume();
+ Intent intent = storageVolume.createAccessIntent(directoryName);
+ startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
+ }
+ });
+
+ // Set onClickListener for the external volumes if exists
+ List<StorageVolume> storageVolumes = mStorageManager.getStorageVolumes();
+ LinearLayout containerVolumes = (LinearLayout) mActivity
+ .findViewById(R.id.container_volumes);
+ for (final StorageVolume volume : storageVolumes) {
+ String volumeDescription = volume.getDescription(mActivity);
+ if (volume.isPrimary()) {
+ // Primary volume area is already added...
+ if (volumeDescription != null) {
+ // ...but with a default name: set it to the real name when available.
+ mPrimaryVolumeNameTextView.setText(volumeDescription);
+ }
+ continue;
+ }
+ LinearLayout volumeArea = (LinearLayout) mActivity.getLayoutInflater()
+ .inflate(R.layout.volume_entry, containerVolumes);
+ TextView volumeName = (TextView) volumeArea.findViewById(R.id.textview_volume_name);
+ volumeName.setText(volumeDescription);
+ Button button = (Button) volumeArea.findViewById(R.id.button_open_directory);
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ String selected = mDirectoriesSpinner.getSelectedItem().toString();
+ String directoryName = getDirectoryName(selected);
+ Intent intent = volume.createAccessIntent(directoryName);
+ startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
+ }
+ });
+ }
+ RecyclerView recyclerView = (RecyclerView) rootView
+ .findViewById(R.id.recyclerview_directory_entries);
+ if (savedInstanceState != null) {
+ mDirectoryEntries = savedInstanceState.getParcelableArrayList(DIRECTORY_ENTRIES_KEY);
+ mCurrentDirectoryTextView.setText(savedInstanceState.getString(SELECTED_DIRECTORY_KEY));
+ mAdapter = new DirectoryEntryAdapter(mDirectoryEntries);
+ if (mAdapter.getItemCount() == 0) {
+ mNothingInDirectoryTextView.setVisibility(View.VISIBLE);
+ }
+ } else {
+ mDirectoryEntries = new ArrayList<>();
+ mAdapter = new DirectoryEntryAdapter();
+ }
+ recyclerView.setAdapter(mAdapter);
+ recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
+ mDirectoriesSpinner = (Spinner) rootView.findViewById(R.id.spinner_directories);
+ ArrayAdapter<CharSequence> directoriesAdapter = ArrayAdapter
+ .createFromResource(getActivity(),
+ R.array.directories, android.R.layout.simple_spinner_item);
+ directoriesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mDirectoriesSpinner.setAdapter(directoriesAdapter);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putString(SELECTED_DIRECTORY_KEY, mCurrentDirectoryTextView.getText().toString());
+ outState.putParcelableArrayList(DIRECTORY_ENTRIES_KEY, mDirectoryEntries);
+ }
+
+ private void updateDirectoryEntries(Uri uri) {
+ mDirectoryEntries.clear();
+ ContentResolver contentResolver = getActivity().getContentResolver();
+ Uri docUri = DocumentsContract.buildDocumentUriUsingTree(uri,
+ DocumentsContract.getTreeDocumentId(uri));
+ Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri,
+ DocumentsContract.getTreeDocumentId(uri));
+
+ try (Cursor docCursor = contentResolver
+ .query(docUri, DIRECTORY_SELECTION, null, null, null)) {
+ while (docCursor != null && docCursor.moveToNext()) {
+ mCurrentDirectoryTextView.setText(docCursor.getString(docCursor.getColumnIndex(
+ DocumentsContract.Document.COLUMN_DISPLAY_NAME)));
+ }
+ }
+
+ try (Cursor childCursor = contentResolver
+ .query(childrenUri, DIRECTORY_SELECTION, null, null, null)) {
+ while (childCursor != null && childCursor.moveToNext()) {
+ DirectoryEntry entry = new DirectoryEntry();
+ entry.fileName = childCursor.getString(childCursor.getColumnIndex(
+ DocumentsContract.Document.COLUMN_DISPLAY_NAME));
+ entry.mimeType = childCursor.getString(childCursor.getColumnIndex(
+ DocumentsContract.Document.COLUMN_MIME_TYPE));
+ mDirectoryEntries.add(entry);
+ }
+
+ if (mDirectoryEntries.isEmpty()) {
+ mNothingInDirectoryTextView.setVisibility(View.VISIBLE);
+ } else {
+ mNothingInDirectoryTextView.setVisibility(View.GONE);
+ }
+ mAdapter.setDirectoryEntries(mDirectoryEntries);
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ private String getDirectoryName(String name) {
+ switch (name) {
+ case "ALARMS":
+ return Environment.DIRECTORY_ALARMS;
+ case "DCIM":
+ return Environment.DIRECTORY_DCIM;
+ case "DOCUMENTS":
+ return Environment.DIRECTORY_DOCUMENTS;
+ case "DOWNLOADS":
+ return Environment.DIRECTORY_DOWNLOADS;
+ case "MOVIES":
+ return Environment.DIRECTORY_MOVIES;
+ case "MUSIC":
+ return Environment.DIRECTORY_MUSIC;
+ case "NOTIFICATIONS":
+ return Environment.DIRECTORY_NOTIFICATIONS;
+ case "PICTURES":
+ return Environment.DIRECTORY_PICTURES;
+ case "PODCASTS":
+ return Environment.DIRECTORY_PODCASTS;
+ case "RINGTONES":
+ return Environment.DIRECTORY_RINGTONES;
+ default:
+ throw new IllegalArgumentException("Invalid directory representation: " + name);
+ }
+ }
+}