samples: Sync prebuilts for nyc-dev

developers/samples/android: f9872ded3bb6cd3c01ab0881a58764f5171b4c64
developers/build: 23e77f8a040c0b35531a68e776363f32fa7feeeb

Change-Id: I93e7c66e0cff22d3852d36a1a1257bb10eec3ba0
This commit is contained in:
Trevor Johns
2016-06-15 10:57:45 -07:00
parent 17be9b742a
commit 5a6b719823
140 changed files with 4432 additions and 285 deletions

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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.
]]>

View File

@@ -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>

View File

@@ -23,9 +23,9 @@ import android.content.IntentFilter;
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 @@ public class ActiveNotificationActivity extends MainActivity {
}
private void findFragment() {
mFragment = (ActiveNotificationFragment) getSupportFragmentManager()
mFragment = (ActiveNotificationsFragment) getSupportFragmentManager()
.findFragmentById(R.id.sample_content_fragment);
}

View File

@@ -23,6 +23,7 @@ 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;
@@ -31,17 +32,31 @@ import android.widget.TextView;
import com.example.android.common.logger.Log;
/**
* A fragment that enables display of notifications.
* A fragment that allows notifications to be enqueued.
*/
public class ActiveNotificationFragment extends Fragment {
public class ActiveNotificationsFragment extends Fragment {
private static final String TAG = "ActiveNotificationFragment";
/**
* 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.
private int mNotificationId = 0;
// 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
@@ -69,7 +84,7 @@ public class ActiveNotificationFragment extends Fragment {
public void onClick(View v) {
switch (v.getId()) {
case R.id.add_notification: {
addNotificationAndReadNumber();
addNotificationAndUpdateSummaries();
break;
}
}
@@ -79,35 +94,74 @@ public class ActiveNotificationFragment extends Fragment {
// [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);
Intent deleteIntent = new Intent(ActiveNotificationsActivity.ACTION_NOTIFICATION_DELETE);
mDeletePendingIntent = PendingIntent.getBroadcast(getActivity(),
2323 /* requestCode */, deleteIntent, 0);
REQUEST_CODE, 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.
* 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 addNotificationAndReadNumber() {
private void addNotificationAndUpdateSummaries() {
// [BEGIN create_notification]
// Create a Notification and notify the system.
final Notification.Builder builder = new Notification.Builder(getActivity())
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);
.setDeleteIntent(mDeletePendingIntent)
.setGroup(NOTIFICATION_GROUP);
final Notification notification = builder.build();
mNotificationManager.notify(++mNotificationId, notification);
mNotificationManager.notify(getNewNotificationId(), notification);
// [END create_notification]
Log.i(TAG, "Add a notification");
updateNotificationSummary();
updateNumberOfNotifications();
}
/**
* Request the current number of notifications from the {@link NotificationManager} and
* 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() {
@@ -121,4 +175,19 @@ public class ActiveNotificationFragment extends Fragment {
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;
}
}

View File

@@ -50,7 +50,7 @@ public class MainActivity extends SampleActivityBase {
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();
}

View File

@@ -161,7 +161,7 @@ public class AddFileActivity extends Activity {
return;
}
long fileSize = Long.parseLong(fileSizeEditTextValue) * mSizeMultiplier;
long fileSize = Integer.parseInt(fileSizeEditTextValue) * mSizeMultiplier;
if (mFileStorage == FileStorage.EXTERNAL && !Utils.isExternalStorageAvailable()) {
Toast toast = Toast.makeText(this,

View File

@@ -442,7 +442,10 @@ public class Camera2RawFragment extends Fragment
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 @@ public class Camera2RawFragment extends Fragment
// 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 &&

View File

@@ -757,4 +757,4 @@ public class Camera2VideoFragment extends Fragment
}
}
}

View File

@@ -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>

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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"/>

View File

@@ -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>

View File

@@ -21,6 +21,7 @@ import android.app.DialogFragment;
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.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import javax.inject.Inject;
/**
* A dialog which uses fingerprint APIs to authenticate the user, and falls back to password
* authentication if fingerprint is not available.
@@ -57,12 +56,8 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
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 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment
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 class FingerprintAuthenticationDialogFragment extends DialogFragment
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = (MainActivity) activity;
mInputMethodManager = mActivity.getSystemService(InputMethodManager.class);
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mActivity);
}
/**

View File

@@ -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);
}
}

View File

@@ -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 @@ public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallba
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 class FingerprintUiHelper extends FingerprintManager.AuthenticationCallba
}
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 @@ public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallba
}
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 @@ public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallba
mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS);
}
@VisibleForTesting
Runnable mResetErrorTextRunnable = new Runnable() {
private Runnable mResetErrorTextRunnable = new Runnable() {
@Override
public void run() {
mErrorTextView.setTextColor(

View File

@@ -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);
}
}

View File

@@ -22,6 +22,7 @@ import android.content.Intent;
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.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
@@ -47,8 +49,8 @@ import javax.crypto.BadPaddingException;
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 @@ public class MainActivity extends Activity {
/** 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 @@ public class MainActivity extends Activity {
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 @@ public class MainActivity extends Activity {
// 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 @@ public class MainActivity extends Activity {
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());

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 @@ public class MessageReplyReceiver extends BroadcastReceiver {
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 @@ public class MessageReplyReceiver extends BroadcastReceiver {
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;
}

View File

@@ -39,11 +39,10 @@ public class MessagingService extends Service {
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 @@ public class MessagingService extends Service {
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 @@ public class MessagingService extends Service {
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 @@ public class MessagingService extends Service {
.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);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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.
*/
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

Some files were not shown because too many files have changed in this diff Show More