samples: Sync prebuilts for nyc-dev
developers/samples/android: f9872ded3bb6cd3c01ab0881a58764f5171b4c64 developers/build: 23e77f8a040c0b35531a68e776363f32fa7feeeb Change-Id: I93e7c66e0cff22d3852d36a1a1257bb10eec3ba0
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
]]>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -757,4 +757,4 @@ public class Camera2VideoFragment extends Fragment
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
55
samples/browseable/DirectBoot/AndroidManifest.xml
Normal 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>
|
||||
13
samples/browseable/DirectBoot/_index.jd
Normal 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>
|
||||
|
After Width: | Height: | Size: 584 B |
|
After Width: | Height: | Size: 610 B |
|
After Width: | Height: | Size: 155 B |
|
After Width: | Height: | Size: 828 B |
BIN
samples/browseable/DirectBoot/res/drawable-hdpi/tile.9.png
Normal file
|
After Width: | Height: | Size: 196 B |
|
After Width: | Height: | Size: 375 B |
|
After Width: | Height: | Size: 388 B |
|
After Width: | Height: | Size: 111 B |
|
After Width: | Height: | Size: 495 B |
|
After Width: | Height: | Size: 768 B |
|
After Width: | Height: | Size: 798 B |
|
After Width: | Height: | Size: 148 B |
|
After Width: | Height: | Size: 856 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 191 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 237 B |
|
After Width: | Height: | Size: 1.4 KiB |
23
samples/browseable/DirectBoot/res/drawable/divider.xml
Normal 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>
|
||||
24
samples/browseable/DirectBoot/res/layout/activity_main.xml
Executable 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" />
|
||||
48
samples/browseable/DirectBoot/res/layout/alarm_row.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
BIN
samples/browseable/DirectBoot/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
samples/browseable/DirectBoot/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
samples/browseable/DirectBoot/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
samples/browseable/DirectBoot/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
samples/browseable/DirectBoot/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
21
samples/browseable/DirectBoot/res/values-v21/base-colors.xml
Normal 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>
|
||||
@@ -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>
|
||||
32
samples/browseable/DirectBoot/res/values/base-strings.xml
Normal 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>
|
||||
19
samples/browseable/DirectBoot/res/values/dimens.xml
Normal 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>
|
||||
26
samples/browseable/DirectBoot/res/values/strings.xml
Normal 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>
|
||||
32
samples/browseable/DirectBoot/res/values/template-dimens.xml
Normal 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>
|
||||
42
samples/browseable/DirectBoot/res/values/template-styles.xml
Normal 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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
86
samples/browseable/MultiWindowPlayground/AndroidManifest.xml
Normal 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>
|
||||
14
samples/browseable/MultiWindowPlayground/_index.jd
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
|
||||
BIN
samples/browseable/MultiWindowPlayground/res/mipmap-hdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
samples/browseable/MultiWindowPlayground/res/mipmap-mdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
samples/browseable/MultiWindowPlayground/res/mipmap-xhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
samples/browseable/MultiWindowPlayground/res/mipmap-xxhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
samples/browseable/MultiWindowPlayground/res/mipmap-xxxhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 7.6 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||