aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NotificationListenerSample/Android.mk33
-rw-r--r--NotificationListenerSample/AndroidManifest.xml37
-rw-r--r--NotificationListenerSample/res/drawable-hdpi/dialog_ic_dismiss.pngbin0 -> 347 bytes
-rw-r--r--NotificationListenerSample/res/drawable-hdpi/icon.pngbin0 -> 7828 bytes
-rw-r--r--NotificationListenerSample/res/drawable-mdpi/dialog_ic_dismiss.pngbin0 -> 313 bytes
-rw-r--r--NotificationListenerSample/res/drawable-mdpi/icon.pngbin0 -> 4287 bytes
-rw-r--r--NotificationListenerSample/res/drawable-xhdpi/dialog_ic_dismiss.pngbin0 -> 398 bytes
-rw-r--r--NotificationListenerSample/res/drawable-xhdpi/icon.pngbin0 -> 12264 bytes
-rw-r--r--NotificationListenerSample/res/drawable-xxhdpi/dialog_ic_dismiss.pngbin0 -> 321 bytes
-rw-r--r--NotificationListenerSample/res/drawable-xxhdpi/icon.pngbin0 -> 22796 bytes
-rw-r--r--NotificationListenerSample/res/layout/item.xml32
-rw-r--r--NotificationListenerSample/res/layout/main.xml50
-rw-r--r--NotificationListenerSample/res/values/strings.xml31
-rw-r--r--NotificationListenerSample/src/com/android/example/notificationlistener/Listener.java251
-rw-r--r--NotificationListenerSample/src/com/android/example/notificationlistener/NotificationListenerActivity.java260
-rw-r--r--NotificationShowcase/AndroidManifest.xml19
-rw-r--r--NotificationShowcase/res/layout/full_screen.xml13
-rw-r--r--NotificationShowcase/res/values/strings.xml3
-rw-r--r--NotificationShowcase/src/com/android/example/notificationshowcase/FullScreenActivity.java60
-rw-r--r--NotificationShowcase/src/com/android/example/notificationshowcase/NotificationService.java266
-rw-r--r--NotificationShowcase/src/com/android/example/notificationshowcase/NotificationShowcaseActivity.java360
-rw-r--r--NotificationShowcase/src/com/android/example/notificationshowcase/PhoneService.java81
-rw-r--r--NotificationShowcase/src/com/android/example/notificationshowcase/ProgressService.java93
-rw-r--r--NotificationShowcase/src/com/android/example/notificationshowcase/ToastService.java72
-rw-r--r--NotificationShowcase/src/com/android/example/notificationshowcase/UpdateService.java75
-rw-r--r--PrintApp/Android.mk2
-rw-r--r--PrintApp/AndroidManifest.xml4
-rw-r--r--PrintApp/res/menu/activity_main.xml4
-rw-r--r--PrintApp/src/foo/bar/print/PrintActivity.java268
-rw-r--r--PrintApp/src/foo/bar/print/PrintedPdfDocument.java160
-rw-r--r--PrintService/Android.mk2
-rw-r--r--PrintService/AndroidManifest.xml21
-rw-r--r--PrintService/res/values/arrays.xml35
-rw-r--r--PrintService/res/values/strings.xml15
-rw-r--r--PrintService/res/xml/printservice.xml3
-rw-r--r--PrintService/src/foo/bar/printservice/CustomPrintOptionsActivity.java55
-rw-r--r--PrintService/src/foo/bar/printservice/MyDialogActivity.java112
-rw-r--r--PrintService/src/foo/bar/printservice/MyPrintService.java495
-rw-r--r--PrintService2/src/foo/bar/printservice2/MyPrintService.java6
-rw-r--r--procstatlog/procstatlog.c2
40 files changed, 2168 insertions, 752 deletions
diff --git a/NotificationListenerSample/Android.mk b/NotificationListenerSample/Android.mk
new file mode 100644
index 0000000..5075a96
--- /dev/null
+++ b/NotificationListenerSample/Android.mk
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_AAPT_FLAGS += -c mdpi,hdpi,xhdpi
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
+LOCAL_PACKAGE_NAME := NotificationListenerSample
+LOCAL_CERTIFICATE := platform
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/NotificationListenerSample/AndroidManifest.xml b/NotificationListenerSample/AndroidManifest.xml
new file mode 100644
index 0000000..e36d422
--- /dev/null
+++ b/NotificationListenerSample/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.example.notificationlistener"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <application android:icon="@drawable/icon" android:label="@string/app_name">
+ <activity android:name=".NotificationListenerActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <service android:name="com.android.example.notificationlistener.Listener"
+ android:label="@string/service_name"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/NotificationListenerSample/res/drawable-hdpi/dialog_ic_dismiss.png b/NotificationListenerSample/res/drawable-hdpi/dialog_ic_dismiss.png
new file mode 100644
index 0000000..1aca4b7
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-hdpi/dialog_ic_dismiss.png
Binary files differ
diff --git a/NotificationListenerSample/res/drawable-hdpi/icon.png b/NotificationListenerSample/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..bc976fa
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/NotificationListenerSample/res/drawable-mdpi/dialog_ic_dismiss.png b/NotificationListenerSample/res/drawable-mdpi/dialog_ic_dismiss.png
new file mode 100644
index 0000000..6a2fb5e
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-mdpi/dialog_ic_dismiss.png
Binary files differ
diff --git a/NotificationListenerSample/res/drawable-mdpi/icon.png b/NotificationListenerSample/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..e8f7538
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/NotificationListenerSample/res/drawable-xhdpi/dialog_ic_dismiss.png b/NotificationListenerSample/res/drawable-xhdpi/dialog_ic_dismiss.png
new file mode 100644
index 0000000..e11f2e3
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-xhdpi/dialog_ic_dismiss.png
Binary files differ
diff --git a/NotificationListenerSample/res/drawable-xhdpi/icon.png b/NotificationListenerSample/res/drawable-xhdpi/icon.png
new file mode 100644
index 0000000..002515c
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-xhdpi/icon.png
Binary files differ
diff --git a/NotificationListenerSample/res/drawable-xxhdpi/dialog_ic_dismiss.png b/NotificationListenerSample/res/drawable-xxhdpi/dialog_ic_dismiss.png
new file mode 100644
index 0000000..8d185f4
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-xxhdpi/dialog_ic_dismiss.png
Binary files differ
diff --git a/NotificationListenerSample/res/drawable-xxhdpi/icon.png b/NotificationListenerSample/res/drawable-xxhdpi/icon.png
new file mode 100644
index 0000000..e78554f
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-xxhdpi/icon.png
Binary files differ
diff --git a/NotificationListenerSample/res/layout/item.xml b/NotificationListenerSample/res/layout/item.xml
new file mode 100644
index 0000000..2d9a5ed
--- /dev/null
+++ b/NotificationListenerSample/res/layout/item.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:padding="4dp">
+ <FrameLayout android:id="@+id/remote_view"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:onClick="launch"
+ />
+ <ImageButton android:id="@+id/dismiss"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|end"
+ android:src="@drawable/dialog_ic_dismiss"
+ android:onClick="dismiss"
+ />
+</FrameLayout> \ No newline at end of file
diff --git a/NotificationListenerSample/res/layout/main.xml b/NotificationListenerSample/res/layout/main.xml
new file mode 100644
index 0000000..44c9da4
--- /dev/null
+++ b/NotificationListenerSample/res/layout/main.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="12dp"
+ >
+ <ListView
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:padding="4dp"
+ android:layout_marginBottom="16dp"
+ android:text="@string/working_as_intended"
+ android:layout_weight="1"
+ android:choiceMode="singleChoice"
+ />
+
+ <TextView android:id="@android:id/empty"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:padding="4dp"
+ android:layout_marginBottom="16dp"
+ android:text="@string/working_as_intended"
+ android:layout_weight="1"
+ />
+ <Button
+ android:id="@+id/launch_settings"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/launch_to_disable"
+ android:onClick="launchSettings"
+ android:layout_weight="0"
+ />
+</LinearLayout>
+
diff --git a/NotificationListenerSample/res/values/strings.xml b/NotificationListenerSample/res/values/strings.xml
new file mode 100644
index 0000000..5c74fda
--- /dev/null
+++ b/NotificationListenerSample/res/values/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <string name="app_name">NotificationListener</string>
+ <string name="long_app_name">Notification Listener Sample</string>
+ <string name="service_name">Sample Notification Listener</string>
+ <string name="working_as_intended">Service is running. Disable it in Security Settings.</string>
+ <string name="waiting_for_content">Service is running. Waiting for notifications to arrive.</string>
+ <string name="nothing_to_see">Service is disabled. You must enabled it to see notifications.</string>
+ <string name="launch_to_disable">Press to disable service</string>
+ <string name="launch_to_enable">Press to enable service</string>
+ <string name="explanation">This app demonstrates use of the Notification Listener Service API.
+ You must enable the listener service in Security Settings to see it work.
+ </string>
+ <string name="disabled">Service is not running.</string>
+ <string name="enable_it">Enable</string>
+ <string name="cancel">Cancel</string>
+</resources>
diff --git a/NotificationListenerSample/src/com/android/example/notificationlistener/Listener.java b/NotificationListenerSample/src/com/android/example/notificationlistener/Listener.java
new file mode 100644
index 0000000..466b084
--- /dev/null
+++ b/NotificationListenerSample/src/com/android/example/notificationlistener/Listener.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.example.notificationlistener;
+
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Message;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.StatusBarNotification;
+import android.support.v4.content.LocalBroadcastManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class Listener extends NotificationListenerService {
+ private static final String TAG = "SampleListener";
+
+ // Message tags
+ private static final int MSG_NOTIFY = 1;
+ private static final int MSG_CANCEL = 2;
+ private static final int MSG_STARTUP = 3;
+ private static final int MSG_ORDER = 4;
+ private static final int MSG_DISMISS = 5;
+ private static final int MSG_LAUNCH = 6;
+ private static final int PAGE = 10;
+
+ static final String ACTION_DISMISS = "com.android.example.notificationlistener.DISMISS";
+ static final String ACTION_LAUNCH = "com.android.example.notificationlistener.LAUNCH";
+ static final String ACTION_REFRESH = "com.android.example.notificationlistener.REFRESH";
+ static final String EXTRA_KEY = "key";
+
+ private static ArrayList<StatusBarNotification> sNotifications;
+
+ public static List<StatusBarNotification> getNotifications() {
+ return sNotifications;
+ }
+
+ private final Ranking mTmpRanking = new Ranking();
+
+ private class Delta {
+ final StatusBarNotification mSbn;
+ final RankingMap mRankingMap;
+
+ public Delta(StatusBarNotification sbn, RankingMap rankingMap) {
+ mSbn = sbn;
+ mRankingMap = rankingMap;
+ }
+ }
+
+ private final Comparator<StatusBarNotification> mRankingComparator =
+ new Comparator<StatusBarNotification>() {
+
+ private final Ranking mLhsRanking = new Ranking();
+ private final Ranking mRhsRanking = new Ranking();
+
+ @Override
+ public int compare(StatusBarNotification lhs, StatusBarNotification rhs) {
+ mRankingMap.getRanking(lhs.getKey(), mLhsRanking);
+ mRankingMap.getRanking(rhs.getKey(), mRhsRanking);
+ return Integer.compare(mLhsRanking.getRank(), mRhsRanking.getRank());
+ }
+ };
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String key = intent.getStringExtra(EXTRA_KEY);
+ int what = MSG_DISMISS;
+ if (ACTION_LAUNCH.equals(intent.getAction())) {
+ what = MSG_LAUNCH;
+ }
+ Log.d(TAG, "received an action broadcast " + intent.getAction());
+ if (!TextUtils.isEmpty(key)) {
+ Log.d(TAG, " on " + key);
+ Message.obtain(mHandler, what, key).sendToTarget();
+ }
+ }
+ };
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ Delta delta = null;
+ if (msg.obj instanceof Delta) {
+ delta = (Delta) msg.obj;
+ }
+
+ switch (msg.what) {
+ case MSG_NOTIFY:
+ Log.i(TAG, "notify: " + delta.mSbn.getKey());
+ synchronized (sNotifications) {
+ boolean exists = mRankingMap.getRanking(delta.mSbn.getKey(), mTmpRanking);
+ if (!exists) {
+ sNotifications.add(delta.mSbn);
+ } else {
+ int position = mTmpRanking.getRank();
+ sNotifications.set(position, delta.mSbn);
+ }
+ mRankingMap = delta.mRankingMap;
+ Collections.sort(sNotifications, mRankingComparator);
+ Log.i(TAG, "finish with: " + sNotifications.size());
+ }
+ LocalBroadcastManager.getInstance(Listener.this)
+ .sendBroadcast(new Intent(ACTION_REFRESH)
+ .putExtra(EXTRA_KEY, delta.mSbn.getKey()));
+ break;
+
+ case MSG_CANCEL:
+ Log.i(TAG, "remove: " + delta.mSbn.getKey());
+ synchronized (sNotifications) {
+ boolean exists = mRankingMap.getRanking(delta.mSbn.getKey(), mTmpRanking);
+ if (exists) {
+ sNotifications.remove(mTmpRanking.getRank());
+ }
+ mRankingMap = delta.mRankingMap;
+ Collections.sort(sNotifications, mRankingComparator);
+ }
+ LocalBroadcastManager.getInstance(Listener.this)
+ .sendBroadcast(new Intent(ACTION_REFRESH));
+ break;
+
+ case MSG_ORDER:
+ Log.i(TAG, "reorder");
+ synchronized (sNotifications) {
+ mRankingMap = delta.mRankingMap;
+ Collections.sort(sNotifications, mRankingComparator);
+ }
+ LocalBroadcastManager.getInstance(Listener.this)
+ .sendBroadcast(new Intent(ACTION_REFRESH));
+ break;
+
+ case MSG_STARTUP:
+ fetchActive();
+ Log.i(TAG, "start with: " + sNotifications.size() + " notifications.");
+ LocalBroadcastManager.getInstance(Listener.this)
+ .sendBroadcast(new Intent(ACTION_REFRESH));
+ break;
+
+ case MSG_DISMISS:
+ if (msg.obj instanceof String) {
+ final String key = (String) msg.obj;
+ mRankingMap.getRanking(key, mTmpRanking);
+ StatusBarNotification sbn = sNotifications.get(mTmpRanking.getRank());
+ if ((sbn.getNotification().flags & Notification.FLAG_AUTO_CANCEL) != 0 &&
+ sbn.getNotification().contentIntent != null) {
+ try {
+ sbn.getNotification().contentIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.d(TAG, "failed to send intent for " + sbn.getKey(), e);
+ }
+ }
+ cancelNotification(key);
+ }
+ break;
+
+ case MSG_LAUNCH:
+ if (msg.obj instanceof String) {
+ final String key = (String) msg.obj;
+ mRankingMap.getRanking(key, mTmpRanking);
+ StatusBarNotification sbn = sNotifications.get(mTmpRanking.getRank());
+ if (sbn.getNotification().contentIntent != null) {
+ try {
+ sbn.getNotification().contentIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.d(TAG, "failed to send intent for " + sbn.getKey(), e);
+ }
+ }
+ if ((sbn.getNotification().flags & Notification.FLAG_AUTO_CANCEL) != 0) {
+ cancelNotification(key);
+ }
+ }
+ break;
+ }
+ }
+ };
+
+ private RankingMap mRankingMap;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.d(TAG, "registering broadcast listener");
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_DISMISS);
+ intentFilter.addAction(ACTION_LAUNCH);
+ LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter);
+ }
+
+ @Override
+ public void onDestroy() {
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mBroadcastReceiver);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onListenerConnected() {
+ Message.obtain(mHandler, MSG_STARTUP).sendToTarget();
+ }
+
+ @Override
+ public void onNotificationRankingUpdate(RankingMap rankingMap) {
+ Message.obtain(mHandler, MSG_ORDER,
+ new Delta(null, rankingMap)).sendToTarget();
+ }
+
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+ Message.obtain(mHandler, MSG_NOTIFY,
+ new Delta(sbn, rankingMap)).sendToTarget();
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+ Message.obtain(mHandler, MSG_CANCEL,
+ new Delta(sbn, rankingMap)).sendToTarget();
+ }
+
+ private void fetchActive() {
+ mRankingMap = getCurrentRanking();
+ sNotifications = new ArrayList<StatusBarNotification>();
+ for (StatusBarNotification sbn : getActiveNotifications()) {
+ sNotifications.add(sbn);
+ }
+ Collections.sort(sNotifications, mRankingComparator);
+ }
+}
diff --git a/NotificationListenerSample/src/com/android/example/notificationlistener/NotificationListenerActivity.java b/NotificationListenerSample/src/com/android/example/notificationlistener/NotificationListenerActivity.java
new file mode 100644
index 0000000..7699d33
--- /dev/null
+++ b/NotificationListenerSample/src/com/android/example/notificationlistener/NotificationListenerActivity.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.example.notificationlistener;
+
+import android.app.AlertDialog;
+import android.app.ListActivity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.provider.Settings.Secure;
+import android.service.notification.StatusBarNotification;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+public class NotificationListenerActivity extends ListActivity {
+ private static final String LISTENER_PATH = "com.android.example.notificationlistener/" +
+ "com.android.example.notificationlistener.Listener";
+ private static final String TAG = "NotificationListenerActivity";
+
+ private Button mLaunchButton;
+ private TextView mEmptyText;
+ private StatusAdaptor mStatusAdaptor;
+ private final BroadcastReceiver mRefreshListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, "update tickle");
+ updateList(intent.getStringExtra(Listener.EXTRA_KEY));
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTitle(R.string.long_app_name);
+ setContentView(R.layout.main);
+ mLaunchButton = (Button) findViewById(R.id.launch_settings);
+ mEmptyText = (TextView) findViewById(android.R.id.empty);
+ mStatusAdaptor = new StatusAdaptor(this);
+ setListAdapter(mStatusAdaptor);
+ }
+
+ @Override
+ protected void onStop() {
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mRefreshListener);
+ super.onStop();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ final IntentFilter intentFilter = new IntentFilter(Listener.ACTION_REFRESH);
+ LocalBroadcastManager.getInstance(this).registerReceiver(mRefreshListener, intentFilter);
+ updateList(null);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ checkEnabled();
+ }
+
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ checkEnabled();
+ }
+
+ private void checkEnabled() {
+ String listeners = Secure.getString(getContentResolver(),
+ "enabled_notification_listeners");
+ if (listeners != null && listeners.contains(LISTENER_PATH)) {
+ mLaunchButton.setText(R.string.launch_to_disable);
+ mEmptyText.setText(R.string.waiting_for_content);
+ } else {
+ mLaunchButton.setText(R.string.launch_to_enable);
+ mEmptyText.setText(R.string.nothing_to_see);
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage(R.string.explanation)
+ .setTitle(R.string.disabled);
+ builder.setPositiveButton(R.string.enable_it, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ launchSettings(null);
+ }
+ });
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.create().show();
+ }
+ }
+
+ public void launchSettings(View v) {
+ startActivityForResult(
+ new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"), 0);
+ }
+
+ public void dismiss(View v) {
+ Log.d(TAG, "clicked dismiss ");
+ Object tag = v.getTag();
+ if (tag instanceof StatusBarNotification) {
+ StatusBarNotification sbn = (StatusBarNotification) tag;
+ Log.d(TAG, " on " + sbn.getKey());
+ LocalBroadcastManager.getInstance(this).
+ sendBroadcast(new Intent(Listener.ACTION_DISMISS)
+ .putExtra(Listener.EXTRA_KEY, sbn.getKey()));
+ }
+ }
+
+ public void launch(View v) {
+ Log.d(TAG, "clicked launch");
+ Object tag = v.getTag();
+ if (tag instanceof StatusBarNotification) {
+ StatusBarNotification sbn = (StatusBarNotification) tag;
+ Log.d(TAG, " on " + sbn.getKey());
+ LocalBroadcastManager.getInstance(this).
+ sendBroadcast(new Intent(Listener.ACTION_LAUNCH)
+ .putExtra(Listener.EXTRA_KEY, sbn.getKey()));
+ }
+ }
+
+ private void updateList(String key) {
+ if (mStatusAdaptor.requiresInitialization()) {
+ final List<StatusBarNotification> notifications = Listener.getNotifications();
+ if (notifications != null) {
+ mStatusAdaptor.init(notifications);
+ }
+ }
+ mStatusAdaptor.update(key);
+ }
+
+ private class StatusAdaptor extends BaseAdapter {
+ private final Context mContext;
+ private List<StatusBarNotification> mNotifications;
+ private HashMap<String, Long> mKeyToId;
+ private HashSet<String> mKeys;
+ private long mNextId;
+ private HashMap<String, View> mRecycledViews;
+ private String mUpdateKey;
+
+ public StatusAdaptor(Context context) {
+ mContext = context;
+ mKeyToId = new HashMap<String, Long>();
+ mKeys = new HashSet<String>();
+ mNextId = 0;
+ mRecycledViews = new HashMap<String, View>();
+ }
+
+ @Override
+ public int getCount() {
+ return mNotifications == null ? 0 : mNotifications.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mNotifications.get(position);
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ final StatusBarNotification sbn = mNotifications.get(position);
+ final String key = sbn.getKey();
+ if (!mKeyToId.containsKey(key)) {
+ mKeyToId.put(key, mNextId);
+ mNextId ++;
+ }
+ return mKeyToId.get(key);
+ }
+
+ @Override
+ public View getView(int position, View view, ViewGroup list) {
+ if (view == null) {
+ view = View.inflate(mContext, R.layout.item, null);
+ }
+ FrameLayout container = (FrameLayout) view.findViewById(R.id.remote_view);
+ View dismiss = view.findViewById(R.id.dismiss);
+ StatusBarNotification sbn = mNotifications.get(position);
+ View child;
+ if (container.getTag() instanceof StatusBarNotification &&
+ container.getChildCount() > 0) {
+ // recycle the view
+ StatusBarNotification old = (StatusBarNotification) container.getTag();
+ if (sbn.getKey().equals(mUpdateKey)) {
+ //this view is out of date, discard it
+ mUpdateKey = null;
+ } else {
+ View content = container.getChildAt(0);
+ container.removeView(content);
+ mRecycledViews.put(old.getKey(), content);
+ }
+ }
+ child = mRecycledViews.get(sbn.getKey());
+ if (child == null) {
+ child = sbn.getNotification().contentView.apply(mContext, null);
+ }
+ container.setTag(sbn);
+ container.removeAllViews();
+ container.addView(child);
+ dismiss.setVisibility(sbn.isClearable() ? View.VISIBLE : View.GONE);
+ dismiss.setTag(sbn);
+ return view;
+ }
+
+ public void update(String key) {
+ if (mNotifications != null) {
+ synchronized (mNotifications) {
+ mKeys.clear();
+ for (int i = 0; i < mNotifications.size(); i++) {
+ mKeys.add(mNotifications.get(i).getKey());
+ }
+ mKeyToId.keySet().retainAll(mKeys);
+ }
+ if (key == null) {
+ mRecycledViews.clear();
+ } else {
+ mUpdateKey = key;
+ mRecycledViews.remove(key);
+ }
+ Log.d(TAG, "notifyDataSetChanged");
+ notifyDataSetChanged();
+ } else {
+ Log.d(TAG, "missed and update");
+ }
+ }
+
+ public boolean requiresInitialization() {
+ return mNotifications == null;
+ }
+
+ public void init(List<StatusBarNotification> notifications) {
+ mNotifications = notifications;
+ }
+ }
+}
diff --git a/NotificationShowcase/AndroidManifest.xml b/NotificationShowcase/AndroidManifest.xml
index a36da78..def9061 100644
--- a/NotificationShowcase/AndroidManifest.xml
+++ b/NotificationShowcase/AndroidManifest.xml
@@ -3,8 +3,12 @@
package="com.android.example.notificationshowcase"
android:versionCode="1"
android:versionName="1.0">
- <uses-sdk android:minSdkVersion="4" />
+ <uses-sdk android:minSdkVersion="4"
+ android:targetSdkVersion="16" />
+ <uses-permission android:name="android.permission.READ_CONTACTS">
+
+ </uses-permission>
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".NotificationShowcaseActivity"
android:label="@string/app_name">
@@ -13,7 +17,16 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <service android:name=".NotificationShowcaseActivity$UpdateService"/>
- <service android:name=".NotificationShowcaseActivity$ProgressService"/>
+ <activity android:name=".FullScreenActivity"
+ android:label="@string/full_screen_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ <service android:name=".NotificationService"/>
+ <service android:name=".UpdateService"/>
+ <service android:name=".ProgressService"/>
+ <service android:name=".PhoneService"/>
+ <service android:name=".ToastService"/>
</application>
</manifest>
diff --git a/NotificationShowcase/res/layout/full_screen.xml b/NotificationShowcase/res/layout/full_screen.xml
new file mode 100644
index 0000000..6ff7552
--- /dev/null
+++ b/NotificationShowcase/res/layout/full_screen.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:src="@drawable/page_hed"
+ android:onClick="dismiss"
+ />
+</FrameLayout> \ No newline at end of file
diff --git a/NotificationShowcase/res/values/strings.xml b/NotificationShowcase/res/values/strings.xml
index c514e2a..80bf103 100644
--- a/NotificationShowcase/res/values/strings.xml
+++ b/NotificationShowcase/res/values/strings.xml
@@ -4,4 +4,7 @@
<string name="app_name">NotificationShowcase</string>
<string name="post_button_label">Post Notifications</string>
<string name="remove_button_label">Remove Notifications</string>
+ <string name="answered">call answered</string>
+ <string name="ignored">call ignored</string>
+ <string name="full_screen_name">Full Screen Activity</string>
</resources>
diff --git a/NotificationShowcase/src/com/android/example/notificationshowcase/FullScreenActivity.java b/NotificationShowcase/src/com/android/example/notificationshowcase/FullScreenActivity.java
new file mode 100644
index 0000000..8286f69
--- /dev/null
+++ b/NotificationShowcase/src/com/android/example/notificationshowcase/FullScreenActivity.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.example.notificationshowcase;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+public class FullScreenActivity extends Activity {
+ private static final String TAG = "NotificationShowcase";
+
+ public static final String EXTRA_ID = "id";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.full_screen);
+ final Intent intent = getIntent();
+ if (intent != null && intent.hasExtra(EXTRA_ID)) {
+ final int id = intent.getIntExtra(EXTRA_ID, -1);
+ if (id >= 0) {
+ NotificationManager noMa =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ noMa.cancel(NotificationService.NOTIFICATION_ID + id);
+ }
+ }
+ }
+
+ public void dismiss(View v) {
+ finish();
+ }
+
+ public static PendingIntent getPendingIntent(Context context, int id) {
+ Intent fullScreenIntent = new Intent(context, FullScreenActivity.class);
+ fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ fullScreenIntent.putExtra(EXTRA_ID, id);
+ PendingIntent pi = PendingIntent.getActivity(
+ context, 22, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ return pi;
+ }
+}
diff --git a/NotificationShowcase/src/com/android/example/notificationshowcase/NotificationService.java b/NotificationShowcase/src/com/android/example/notificationshowcase/NotificationService.java
new file mode 100644
index 0000000..0b83fa0
--- /dev/null
+++ b/NotificationShowcase/src/com/android/example/notificationshowcase/NotificationService.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.example.notificationshowcase;
+
+import android.app.IntentService;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.support.v4.app.NotificationCompat;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.StyleSpan;
+import android.util.Log;
+import android.view.View;
+
+import java.util.ArrayList;
+
+public class NotificationService extends IntentService {
+
+ private static final String TAG = "NotificationService";
+
+ public static final String ACTION_CREATE = "create";
+ public static final int NOTIFICATION_ID = 31338;
+
+ public NotificationService() {
+ super(TAG);
+ }
+
+ public NotificationService(String name) {
+ super(name);
+ }
+
+ private static Bitmap getBitmap(Context context, int resId) {
+ int largeIconWidth = (int) context.getResources()
+ .getDimension(R.dimen.notification_large_icon_width);
+ int largeIconHeight = (int) context.getResources()
+ .getDimension(R.dimen.notification_large_icon_height);
+ Drawable d = context.getResources().getDrawable(resId);
+ Bitmap b = Bitmap.createBitmap(largeIconWidth, largeIconHeight, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(b);
+ d.setBounds(0, 0, largeIconWidth, largeIconHeight);
+ d.draw(c);
+ return b;
+ }
+
+ private static PendingIntent makeEmailIntent(Context context, String who) {
+ final Intent intent = new Intent(android.content.Intent.ACTION_SENDTO,
+ Uri.parse("mailto:" + who));
+ return PendingIntent.getActivity(
+ context, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+ public static Notification makeBigTextNotification(Context context, int update, int id,
+ long when) {
+ String personUri = null;
+ Cursor c = null;
+ try {
+ String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY };
+ String selections = ContactsContract.Contacts.DISPLAY_NAME + " = 'Mike Cleron'";
+ final ContentResolver contentResolver = context.getContentResolver();
+ c = contentResolver.query(ContactsContract.Contacts.CONTENT_URI,
+ projection, selections, null, null);
+ if (c != null && c.getCount() > 0) {
+ c.moveToFirst();
+ int lookupIdx = c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
+ int idIdx = c.getColumnIndex(ContactsContract.Contacts._ID);
+ String lookupKey = c.getString(lookupIdx);
+ long contactId = c.getLong(idIdx);
+ Uri lookupUri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
+ personUri = lookupUri.toString();
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ if (TextUtils.isEmpty(personUri)) {
+ Log.w(TAG, "failed to find contact for Mike Cleron");
+ } else {
+ Log.w(TAG, "Mike Cleron is " + personUri);
+ }
+
+ String addendum = update > 0 ? "(updated) " : "";
+ String longSmsText = "Hey, looks like\nI'm getting kicked out of this conference" +
+ " room";
+ if (update > 1) {
+ longSmsText += ", so stay in the hangout and I'll rejoin in about 5-10 minutes" +
+ ". If you don't see me, assume I got pulled into another meeting. And" +
+ " now \u2026 I have to find my shoes.";
+ }
+ if (update > 2) {
+ when = System.currentTimeMillis();
+ }
+ NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle();
+ bigTextStyle.bigText(addendum + longSmsText);
+ Notification bigText = new NotificationCompat.Builder(context)
+ .setContentTitle(addendum + "Mike Cleron")
+ .setContentIntent(ToastService.getPendingIntent(context, "Clicked on bigText"))
+ .setContentText(addendum + longSmsText)
+ .setTicker(addendum + "Mike Cleron: " + longSmsText)
+ .setWhen(when)
+ .setLargeIcon(getBitmap(context, R.drawable.bucket))
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .addAction(R.drawable.ic_media_next,
+ "update: " + update,
+ UpdateService.getPendingIntent(context, update + 1, id, when))
+ .setSmallIcon(R.drawable.stat_notify_talk_text)
+ .setStyle(bigTextStyle)
+ .setDefaults(Notification.DEFAULT_SOUND)
+ .addPerson(personUri)
+ .build();
+ return bigText;
+ }
+
+ public static Notification makeUploadNotification(Context context, int progress, long when) {
+ NotificationCompat.Builder uploadNotification = new NotificationCompat.Builder(context)
+ .setContentTitle("File Upload")
+ .setContentText("foo.txt")
+ .setPriority(NotificationCompat.PRIORITY_MIN)
+ .setContentIntent(ToastService.getPendingIntent(context, "Clicked on Upload"))
+ .setWhen(when)
+ .setSmallIcon(R.drawable.ic_menu_upload)
+ .setProgress(100, Math.min(progress, 100), false);
+ return uploadNotification.build();
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ ArrayList<Notification> mNotifications = new ArrayList<Notification>();
+ NotificationManager noMa =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+
+ int bigtextId = mNotifications.size();
+ mNotifications.add(makeBigTextNotification(this, 0, bigtextId, System.currentTimeMillis()));
+
+ int uploadId = mNotifications.size();
+ long uploadWhen = System.currentTimeMillis();
+ mNotifications.add(makeUploadNotification(this, 10, uploadWhen));
+
+ int phoneId = mNotifications.size();
+ final PendingIntent fullscreenIntent = FullScreenActivity.getPendingIntent(this, phoneId);
+ Notification phoneCall = new NotificationCompat.Builder(this)
+ .setContentTitle("Incoming call")
+ .setContentText("Matias Duarte")
+ .setLargeIcon(getBitmap(this, R.drawable.matias_hed))
+ .setSmallIcon(R.drawable.stat_sys_phone_call)
+ .setPriority(NotificationCompat.PRIORITY_MAX)
+ .setContentIntent(fullscreenIntent)
+ .setFullScreenIntent(fullscreenIntent, true)
+ .addAction(R.drawable.ic_dial_action_call, "Answer",
+ PhoneService.getPendingIntent(this, phoneId, PhoneService.ACTION_ANSWER))
+ .addAction(R.drawable.ic_end_call, "Ignore",
+ PhoneService.getPendingIntent(this, phoneId, PhoneService.ACTION_IGNORE))
+ .setOngoing(true)
+ .addPerson(Uri.fromParts("tel", "1 (617) 555-1212", null).toString())
+ .build();
+ mNotifications.add(phoneCall);
+
+ mNotifications.add(new NotificationCompat.Builder(this)
+ .setContentTitle("Stopwatch PRO")
+ .setContentText("Counting up")
+ .setContentIntent(ToastService.getPendingIntent(this, "Clicked on Stopwatch"))
+ .setSmallIcon(R.drawable.stat_notify_alarm)
+ .setUsesChronometer(true)
+ .build());
+
+ mNotifications.add(new NotificationCompat.Builder(this)
+ .setContentTitle("J Planning")
+ .setContentText("The Botcave")
+ .setWhen(System.currentTimeMillis())
+ .setSmallIcon(R.drawable.stat_notify_calendar)
+ .setContentIntent(ToastService.getPendingIntent(this, "Clicked on calendar event"))
+ .setContentInfo("7PM")
+ .addAction(R.drawable.stat_notify_snooze, "+10 min",
+ ToastService.getPendingIntent(this, "snoozed 10 min"))
+ .addAction(R.drawable.stat_notify_snooze_longer, "+1 hour",
+ ToastService.getPendingIntent(this, "snoozed 1 hr"))
+ .addAction(R.drawable.stat_notify_email, "Email",
+ makeEmailIntent(this,
+ "gabec@example.com,mcleron@example.com,dsandler@example.com"))
+ .build());
+
+ BitmapDrawable d =
+ (BitmapDrawable) getResources().getDrawable(R.drawable.romainguy_rockaway);
+ mNotifications.add(new NotificationCompat.BigPictureStyle(
+ new NotificationCompat.Builder(this)
+ .setContentTitle("Romain Guy")
+ .setContentText("I was lucky to find a Canon 5D Mk III at a local Bay Area "
+ + "store last week but I had not been able to try it in the field "
+ + "until tonight. After a few days of rain the sky finally cleared "
+ + "up. Rockaway Beach did not disappoint and I was finally able to "
+ + "see what my new camera feels like when shooting landscapes.")
+ .setSmallIcon(R.drawable.ic_stat_gplus)
+ .setContentIntent(
+ ToastService.getPendingIntent(this, "Clicked on bigPicture"))
+ .setLargeIcon(getBitmap(this, R.drawable.romainguy_hed))
+ .addAction(R.drawable.add, "Add to Gallery",
+ ToastService.getPendingIntent(this, "added! (just kidding)"))
+ .setSubText("talk rocks!"))
+ .bigPicture(d.getBitmap())
+ .build());
+
+ // Note: this may conflict with real email notifications
+ StyleSpan bold = new StyleSpan(Typeface.BOLD);
+ SpannableString line1 = new SpannableString("Alice: hey there!");
+ line1.setSpan(bold, 0, 5, 0);
+ SpannableString line2 = new SpannableString("Bob: hi there!");
+ line2.setSpan(bold, 0, 3, 0);
+ SpannableString line3 = new SpannableString("Charlie: Iz IN UR EMAILZ!!");
+ line3.setSpan(bold, 0, 7, 0);
+ mNotifications.add(new NotificationCompat.InboxStyle(
+ new NotificationCompat.Builder(this)
+ .setContentTitle("24 new messages")
+ .setContentText("You have mail!")
+ .setSubText("test.hugo2@gmail.com")
+ .setContentIntent(ToastService.getPendingIntent(this, "Clicked on Email"))
+ .setSmallIcon(R.drawable.stat_notify_email))
+ .setSummaryText("+21 more")
+ .addLine(line1)
+ .addLine(line2)
+ .addLine(line3)
+ .build());
+
+ mNotifications.add(new NotificationCompat.Builder(this)
+ .setContentTitle("Twitter")
+ .setContentText("New mentions")
+ .setContentIntent(ToastService.getPendingIntent(this, "Clicked on Twitter"))
+ .setSmallIcon(R.drawable.twitter_icon)
+ .setNumber(15)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .build());
+
+
+ for (int i=0; i<mNotifications.size(); i++) {
+ noMa.notify(NOTIFICATION_ID + i, mNotifications.get(i));
+ }
+
+ ProgressService.startProgressUpdater(this, uploadId, uploadWhen, 0);
+ }
+}
diff --git a/NotificationShowcase/src/com/android/example/notificationshowcase/NotificationShowcaseActivity.java b/NotificationShowcase/src/com/android/example/notificationshowcase/NotificationShowcaseActivity.java
index ee060ce..1e0fa20 100644
--- a/NotificationShowcase/src/com/android/example/notificationshowcase/NotificationShowcaseActivity.java
+++ b/NotificationShowcase/src/com/android/example/notificationshowcase/NotificationShowcaseActivity.java
@@ -3,372 +3,20 @@
package com.android.example.notificationshowcase;
-import java.util.ArrayList;
import android.app.Activity;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Typeface;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Binder;
import android.os.Bundle;
-import android.os.IBinder;
-import android.support.v4.app.NotificationCompat;
-import android.text.SpannableString;
-import android.text.style.StyleSpan;
-import android.util.Log;
-import android.view.View;
-import android.widget.Toast;
public class NotificationShowcaseActivity extends Activity {
private static final String TAG = "NotificationShowcase";
-
- private static final int NOTIFICATION_ID = 31338;
-
- private static int bigtextId;
- private static int uploadId;
-
- private static final boolean FIRE_AND_FORGET = true;
-
- public static class ToastFeedbackActivity extends Activity {
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- Intent i = getIntent();
- Log.v(TAG, "clicked a thing! intent=" + i.toString());
- if (i.hasExtra("text")) {
- final String text = i.getStringExtra("text");
- Toast.makeText(this, text, Toast.LENGTH_LONG).show();
- }
- finish();
- }
- }
-
- public static class UpdateService extends Service {
- @Override
- public IBinder onBind(Intent intent) {
- Log.v(TAG, "onbind");
- return null;
- }
-
- @Override
- public void onStart(Intent i, int startId) {
- super.onStart(i, startId);
- try {
- // allow the user close the shade, if they want to test that.
- Thread.sleep(3000);
- } catch (Exception e) {
- }
- Log.v(TAG, "clicked a thing! intent=" + i.toString());
- if (i.hasExtra("id") && i.hasExtra("when")) {
- final int id = i.getIntExtra("id", 0);
- if (id == bigtextId) {
- NotificationManager noMa =
- (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- final int update = i.getIntExtra("update", 0);
- final long when = i.getLongExtra("when", 0L);
- Log.v(TAG, "id: " + id + " when: " + when + " update: " + update);
- noMa.notify(NOTIFICATION_ID + id,
- makeBigTextNotification(this, update, id, when));
- }
- } else {
- Log.v(TAG, "id extra was " + (i.hasExtra("id") ? "present" : "missing"));
- Log.v(TAG, "when extra was " + (i.hasExtra("when") ? "present" : "missing"));
- }
- }
- }
-
- public static class ProgressService extends Service {
- @Override
- public IBinder onBind(Intent intent) {
- Log.v(TAG, "onbind");
- return null;
- }
-
- @Override
- public void onStart(Intent i, int startId) {
- super.onStart(i, startId);
- if (i.hasExtra("id") && i.hasExtra("when") && i.hasExtra("progress")) {
- final int id = i.getIntExtra("id", 0);
- if (id == uploadId) {
- final long when = i.getLongExtra("when", 0L);
- int progress = i.getIntExtra("progress", 0);
- NotificationManager noMa = (NotificationManager)
- getSystemService(Context.NOTIFICATION_SERVICE);
- while (progress <= 100) {
- try {
- // allow the user close the shade, if they want to test that.
- Thread.sleep(1000);
- } catch (Exception e) {
- }
- Log.v(TAG, "id: " + id + " when: " + when + " progress: " + progress);
- noMa.notify(NOTIFICATION_ID + id,
- makeUploadNotification(this, progress, id, when));
- progress+=10;
- }
- }
- } else {
- Log.v(TAG, "id extra " + (i.hasExtra("id") ? "present" : "missing"));
- Log.v(TAG, "when extra " + (i.hasExtra("when") ? "present" : "missing"));
- Log.v(TAG, "progress extra " + (i.hasExtra("progress") ? "present" : "missing"));
- }
- }
- }
-
- private ArrayList<Notification> mNotifications = new ArrayList<Notification>();
- NotificationManager mNoMa;
-
- static int mLargeIconWidth, mLargeIconHeight;
- private static Bitmap getBitmap(Context context, int resId) {
- Drawable d = context.getResources().getDrawable(resId);
- Bitmap b = Bitmap.createBitmap(mLargeIconWidth, mLargeIconHeight, Bitmap.Config.ARGB_8888);
- Canvas c = new Canvas(b);
- d.setBounds(0, 0, mLargeIconWidth, mLargeIconHeight);
- d.draw(c);
- return b;
- }
-
- private static PendingIntent makeToastIntent(Context context, String s) {
- Intent toastIntent = new Intent(context, ToastFeedbackActivity.class);
- toastIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- toastIntent.putExtra("text", s);
- PendingIntent pi = PendingIntent.getActivity(
- context, 58, toastIntent, PendingIntent.FLAG_CANCEL_CURRENT);
- return pi;
- }
-
- private static PendingIntent makeEmailIntent(Context context, String who) {
- final Intent intent = new Intent(android.content.Intent.ACTION_SENDTO,
- Uri.parse("mailto:" + who));
- return PendingIntent.getActivity(
- context, 0, intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
- }
-
- // this is a service, it will only close the notification shade if used as a contentIntent.
- private static int updateId = 3000;
- private static PendingIntent makeUpdateIntent(Context context, int update, int id, long when) {
- Intent updateIntent = new Intent();
- updateIntent.setComponent(
- new ComponentName(context, UpdateService.class));
- updateIntent.putExtra("id", id);
- updateIntent.putExtra("when", when);
- updateIntent.putExtra("update", update);
- Log.v(TAG, "added id extra " + id);
- Log.v(TAG, "added when extra " + when);
- PendingIntent pi = PendingIntent.getService(
- context, updateId++, updateIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- return pi;
- }
-
- private static Notification makeBigTextNotification(Context context, int update, int id,
- long when) {
- String addendum = update > 0 ? "(updated) " : "";
- String longSmsText = "Hey, looks like\nI'm getting kicked out of this conference" +
- " room";
- if (update > 1) {
- longSmsText += ", so stay in the hangout and I'll rejoin in about 5-10 minutes" +
- ". If you don't see me, assume I got pulled into another meeting. And" +
- " now \u2026 I have to find my shoes. Four score and seven years "+
- "ago our fathers brought forth on this continent, a new nation, conceived "+
- "in Liberty, and dedicated to the proposition that all men are created "+
- "equal. Now we are engaged in a great civil war, testing whether that "+
- "nation, or any nation so conceived and so dedicated, can long "+
- "endure. We are met on a great battle-field of that war. We have come "+
- "to dedicate a portion of that field, as a final resting place for "+
- "those who here gave their lives that that nation might live. It is "+
- "altogether fitting and proper that we should do this.But, in a larger "+
- "sense, we can not dedicate -- we can not consecrate -- we can not "+
- "hallow -- this ground.The brave men, living and dead, who struggled "+
- "here, have consecrated it, far above our poor power to add or detract."+
- " The world will little note, nor long remember what we say here, but "+
- "it can never forget what they did here. It is for us the living, rather,"+
- " to be dedicated here to the unfinished work which they who fought "+
- "here have thus far so nobly advanced.It is rather for us to be here "+
- "dedicated to the great task remaining before us -- that from these "+
- "honored dead we take increased devotion to that cause for which they "+
- "gave the last full measure of devotion -- that we here highly resolve "+
- "that these dead shall not have died in vain -- that this nation, under "+
- "God, shall have a new birth of freedom -- and that government of "+
- "the people, by the people, for the people, shall not perish from the earth.";
- }
- if (update > 2) {
- when = System.currentTimeMillis();
- }
- NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle();
- bigTextStyle.bigText(addendum + longSmsText);
- NotificationCompat.Builder bigTextNotification = new NotificationCompat.Builder(context)
- .setContentTitle(addendum + "Mike Cleron")
- .setContentIntent(makeToastIntent(context, "Clicked on bigText"))
- .setContentText(addendum + longSmsText)
- .setTicker(addendum + "Mike Cleron: " + longSmsText)
- .setWhen(when)
- .setLargeIcon(getBitmap(context, R.drawable.bucket))
- .setPriority(NotificationCompat.PRIORITY_HIGH)
- .addAction(R.drawable.ic_media_next,
- "update: " + update,
- makeUpdateIntent(context, update+1, id, when))
- .setSmallIcon(R.drawable.stat_notify_talk_text)
- .setStyle(bigTextStyle);
- return bigTextNotification.build();
- }
-
- // this is a service, it will only close the notification shade if used as a contentIntent.
- private static void startProgressUpdater(Context context, int progress, int id, long when) {
- Intent progressIntent = new Intent();
- progressIntent.setComponent(new ComponentName(context, ProgressService.class));
- progressIntent.putExtra("id", id);
- progressIntent.putExtra("when", when);
- progressIntent.putExtra("progress", progress);
- context.startService(progressIntent);
- }
-
- private static Notification makeUploadNotification(Context context, int progress, int id,
- long when) {
- NotificationCompat.Builder uploadNotification = new NotificationCompat.Builder(context)
- .setContentTitle("File Upload")
- .setContentText("foo.txt")
- .setPriority(NotificationCompat.PRIORITY_MIN)
- .setContentIntent(makeToastIntent(context, "Clicked on Upload"))
- .setWhen(when)
- .setSmallIcon(R.drawable.ic_menu_upload)
- .setProgress(100, Math.min(progress, 100), false);
- return uploadNotification.build();
- }
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
-
- mLargeIconWidth = (int) getResources().getDimension(R.dimen.notification_large_icon_width);
- mLargeIconHeight = (int) getResources().getDimension(R.dimen.notification_large_icon_height);
-
- mNoMa = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-
- bigtextId = mNotifications.size();
- mNotifications.add(makeBigTextNotification(this, 0, bigtextId,
- System.currentTimeMillis()));
-
- uploadId = mNotifications.size();
- long uploadWhen = System.currentTimeMillis();
- mNotifications.add(makeUploadNotification(this, 10, uploadId, uploadWhen));
-
- mNotifications.add(new NotificationCompat.Builder(this)
- .setContentTitle("Incoming call")
- .setContentText("Matias Duarte")
- .setLargeIcon(getBitmap(this, R.drawable.matias_hed))
- .setSmallIcon(R.drawable.stat_sys_phone_call)
- .setPriority(NotificationCompat.PRIORITY_MAX)
- .setContentIntent(makeToastIntent(this, "Clicked on Matias"))
- .addAction(R.drawable.ic_dial_action_call, "Answer", makeToastIntent(this, "call answered"))
- .addAction(R.drawable.ic_end_call, "Ignore", makeToastIntent(this, "call ignored"))
- .setAutoCancel(true)
- .build());
-
- mNotifications.add(new NotificationCompat.Builder(this)
- .setContentTitle("Stopwatch PRO")
- .setContentText("Counting up")
- .setContentIntent(makeToastIntent(this, "Clicked on Stopwatch"))
- .setSmallIcon(R.drawable.stat_notify_alarm)
- .setUsesChronometer(true)
- .build());
-
- mNotifications.add(new NotificationCompat.Builder(this)
- .setContentTitle("J Planning")
- .setContentText("The Botcave")
- .setWhen(System.currentTimeMillis())
- .setSmallIcon(R.drawable.stat_notify_calendar)
- .setContentIntent(makeToastIntent(this, "tapped in the calendar event"))
- .setContentInfo("7PM")
- .addAction(R.drawable.stat_notify_snooze, "+10 min",
- makeToastIntent(this, "snoozed 10 min"))
- .addAction(R.drawable.stat_notify_snooze_longer, "+1 hour",
- makeToastIntent(this, "snoozed 1 hr"))
- .addAction(R.drawable.stat_notify_email, "Email",
- makeEmailIntent(this,
- "gabec@example.com,mcleron@example.com,dsandler@example.com"))
- .build());
-
- BitmapDrawable d =
- (BitmapDrawable) getResources().getDrawable(R.drawable.romainguy_rockaway);
- mNotifications.add(new NotificationCompat.BigPictureStyle(
- new NotificationCompat.Builder(this)
- .setContentTitle("Romain Guy")
- .setContentText("I was lucky to find a Canon 5D Mk III at a local Bay Area store last "
- + "week but I had not been able to try it in the field until tonight. After a "
- + "few days of rain the sky finally cleared up. Rockaway Beach did not disappoint "
- + "and I was finally able to see what my new camera feels like when shooting "
- + "landscapes.")
- .setSmallIcon(R.drawable.ic_stat_gplus)
- .setContentIntent(makeToastIntent(this, "Clicked on bigPicture"))
- .setLargeIcon(getBitmap(this, R.drawable.romainguy_hed))
- .addAction(R.drawable.add, "Add to Gallery",
- makeToastIntent(this, "added! (just kidding)"))
- .setSubText("talk rocks!")
- )
- .bigPicture(d.getBitmap())
- .build());
-
- // Note: this may conflict with real email notifications
- StyleSpan bold = new StyleSpan(Typeface.BOLD);
- SpannableString line1 = new SpannableString("Alice: hey there!");
- line1.setSpan(bold, 0, 5, 0);
- SpannableString line2 = new SpannableString("Bob: hi there!");
- line2.setSpan(bold, 0, 3, 0);
- SpannableString line3 = new SpannableString("Charlie: Iz IN UR EMAILZ!!");
- line3.setSpan(bold, 0, 7, 0);
- mNotifications.add(new NotificationCompat.InboxStyle(
- new NotificationCompat.Builder(this)
- .setContentTitle("24 new messages")
- .setContentText("You have mail!")
- .setSubText("test.hugo2@gmail.com")
- .setContentIntent(makeToastIntent(this, "Clicked on Email"))
- .setSmallIcon(R.drawable.stat_notify_email))
- .setSummaryText("+21 more")
- .addLine(line1)
- .addLine(line2)
- .addLine(line3)
- .build());
-
- mNotifications.add(new NotificationCompat.Builder(this)
- .setContentTitle("Twitter")
- .setContentText("New mentions")
- .setContentIntent(makeToastIntent(this, "Clicked on Twitter"))
- .setSmallIcon(R.drawable.twitter_icon)
- .setNumber(15)
- .setPriority(NotificationCompat.PRIORITY_LOW)
- .build());
-
- if (FIRE_AND_FORGET) {
- doPost(null);
- startProgressUpdater(this, 10, uploadId, uploadWhen);
- finish();
- }
- }
-
- public void doPost(View v) {
- for (int i=0; i<mNotifications.size(); i++) {
- mNoMa.notify(NOTIFICATION_ID + i, mNotifications.get(i));
- }
- }
-
- public void doRemove(View v) {
- for (int i=0; i<mNotifications.size(); i++) {
- mNoMa.cancel(NOTIFICATION_ID + i);
- }
+ Intent intent = new Intent(NotificationService.ACTION_CREATE);
+ intent.setComponent(new ComponentName(this, NotificationService.class));
+ startService(intent);
+ finish();
}
}
diff --git a/NotificationShowcase/src/com/android/example/notificationshowcase/PhoneService.java b/NotificationShowcase/src/com/android/example/notificationshowcase/PhoneService.java
new file mode 100644
index 0000000..0b4f3bd
--- /dev/null
+++ b/NotificationShowcase/src/com/android/example/notificationshowcase/PhoneService.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.example.notificationshowcase;
+
+import android.app.IntentService;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.util.Log;
+import android.widget.Toast;
+
+public class PhoneService extends IntentService {
+
+ private static final String TAG = "PhoneService";
+
+ public static final String ACTION_ANSWER = "answer";
+ public static final String ACTION_IGNORE = "ignore";
+
+ public static final String EXTRA_ID = "id";
+
+ private Handler handler;
+
+ public PhoneService() {
+ super(TAG);
+ }
+ public PhoneService(String name) {
+ super(name);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ handler = new Handler();
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ Log.v(TAG, "clicked a thing! intent=" + intent.toString());
+ int res = ACTION_ANSWER.equals(intent.getAction()) ? R.string.answered : R.string.ignored;
+ final String text = getString(res);
+ final int id = intent.getIntExtra(EXTRA_ID, -1);
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(PhoneService.this, text, Toast.LENGTH_LONG).show();
+ if (id >= 0) {
+ NotificationManager noMa =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ noMa.cancel(NotificationService.NOTIFICATION_ID + id);
+ }
+ Log.v(TAG, "phone toast " + text);
+ }
+ });
+ }
+
+ public static PendingIntent getPendingIntent(Context context, int id, String action) {
+ Intent phoneIntent = new Intent(context, PhoneService.class);
+ phoneIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ phoneIntent.setAction(action);
+ phoneIntent.putExtra(EXTRA_ID, id);
+ PendingIntent pi = PendingIntent.getService(
+ context, 58, phoneIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ return pi;
+ }
+}
diff --git a/NotificationShowcase/src/com/android/example/notificationshowcase/ProgressService.java b/NotificationShowcase/src/com/android/example/notificationshowcase/ProgressService.java
new file mode 100644
index 0000000..799708d
--- /dev/null
+++ b/NotificationShowcase/src/com/android/example/notificationshowcase/ProgressService.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.example.notificationshowcase;
+
+import android.app.IntentService;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Log;
+
+public class ProgressService extends IntentService {
+
+ private static final String TAG = "ProgressService";
+
+ private static final String ACTION_PROGRESS = "progress";
+
+ private Handler handler;
+
+ public ProgressService() {
+ super(TAG);
+ }
+ public ProgressService(String name) {
+ super(name);
+ }
+
+ class UpdateRunnable implements Runnable {
+
+ private final int mId;
+ private final long mWhen;
+ private int mProgress;
+
+ UpdateRunnable(int id, long when, int progress) {
+ mId = id;
+ mWhen = when;
+ mProgress = progress;
+ }
+
+ @Override
+ public void run() { NotificationManager noMa = (NotificationManager)
+ getSystemService(Context.NOTIFICATION_SERVICE);
+ Log.v(TAG, "id: " + mId + " when: " + mWhen + " progress: " + mProgress);
+ noMa.notify(NotificationService.NOTIFICATION_ID + mId,
+ NotificationService.makeUploadNotification(
+ ProgressService.this, mProgress, mWhen));
+ mProgress += 10;
+ if (mProgress <= 100) {
+ handler.postDelayed(this, 1000);
+ }
+ }
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ handler = new Handler();
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ final int id = intent.getIntExtra("id", 0);
+ final long when = intent.getLongExtra("when", 0L);
+ int progress = intent.getIntExtra("progress", 0);
+ handler.postDelayed(new UpdateRunnable(id, when, progress), 1000);
+ }
+
+ public static void startProgressUpdater(Context context, int id, long when, int progress) {
+ Intent progressIntent = new Intent(context, ProgressService.class);
+ progressIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ progressIntent.setAction(ACTION_PROGRESS);
+ progressIntent.putExtra("id", id);
+ progressIntent.putExtra("when", when);
+ progressIntent.putExtra("progress", progress);
+ context.startService(progressIntent);
+ }
+}
diff --git a/NotificationShowcase/src/com/android/example/notificationshowcase/ToastService.java b/NotificationShowcase/src/com/android/example/notificationshowcase/ToastService.java
new file mode 100644
index 0000000..6ecfeb2
--- /dev/null
+++ b/NotificationShowcase/src/com/android/example/notificationshowcase/ToastService.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.example.notificationshowcase;
+
+import android.app.IntentService;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.util.Log;
+import android.widget.Toast;
+
+public class ToastService extends IntentService {
+
+ private static final String TAG = "ToastService";
+
+ private static final String ACTION_TOAST = "toast";
+
+ private Handler handler;
+
+ public ToastService() {
+ super(TAG);
+ }
+ public ToastService(String name) {
+ super(name);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ handler = new Handler();
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ Log.v(TAG, "clicked a thing! intent=" + intent.toString());
+ if (intent.hasExtra("text")) {
+ final String text = intent.getStringExtra("text");
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(ToastService.this, text, Toast.LENGTH_LONG).show();
+ Log.v(TAG, "toast " + text);
+ }
+ });
+ }
+ }
+
+ public static PendingIntent getPendingIntent(Context context, String text) {
+ Intent toastIntent = new Intent(context, ToastService.class);
+ toastIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ toastIntent.setAction(ACTION_TOAST + ":" + text); // one per toast message
+ toastIntent.putExtra("text", text);
+ PendingIntent pi = PendingIntent.getService(
+ context, 58, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ return pi;
+ }
+}
diff --git a/NotificationShowcase/src/com/android/example/notificationshowcase/UpdateService.java b/NotificationShowcase/src/com/android/example/notificationshowcase/UpdateService.java
new file mode 100644
index 0000000..23b8de5
--- /dev/null
+++ b/NotificationShowcase/src/com/android/example/notificationshowcase/UpdateService.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.example.notificationshowcase;
+
+import android.app.IntentService;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class UpdateService extends IntentService {
+
+ private static final String TAG = "UpdateService";
+
+ private static final String ACTION_UPDATE = "update";
+
+ public UpdateService() {
+ super(TAG);
+ }
+
+ public UpdateService(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+
+ try {
+ // allow the user close the shade, if they want to test that.
+ Thread.sleep(3000);
+ } catch (Exception e) {
+ }
+ Log.v(TAG, "clicked a thing! intent=" + intent.toString());
+ if (intent.hasExtra("id") && intent.hasExtra("when")) {
+ final int id = intent.getIntExtra("id", 0);
+ NotificationManager noMa =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ final int update = intent.getIntExtra("update", 0);
+ final long when = intent.getLongExtra("when", 0L);
+ Log.v(TAG, "id: " + id + " when: " + when + " update: " + update);
+ noMa.notify(NotificationService.NOTIFICATION_ID + id,
+ NotificationService.makeBigTextNotification(this, update, id, when));
+ } else {
+ Log.v(TAG, "id extra was " + (intent.hasExtra("id") ? "present" : "missing"));
+ Log.v(TAG, "when extra was " + (intent.hasExtra("when") ? "present" : "missing"));
+ }
+ }
+
+ public static PendingIntent getPendingIntent(Context context, int update, int id, long when) {
+ Intent updateIntent = new Intent(context, UpdateService.class);
+ updateIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ updateIntent.setAction(ACTION_UPDATE);
+ updateIntent.putExtra("id", id);
+ updateIntent.putExtra("when", when);
+ updateIntent.putExtra("update", update);
+ PendingIntent pi = PendingIntent.getService(
+ context, 58, updateIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ return pi;
+ }
+}
diff --git a/PrintApp/Android.mk b/PrintApp/Android.mk
index 7cea0ab..5e1c4fa 100644
--- a/PrintApp/Android.mk
+++ b/PrintApp/Android.mk
@@ -8,6 +8,4 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := Print
-LOCAL_CERTIFICATE := platform
-
include $(BUILD_PACKAGE)
diff --git a/PrintApp/AndroidManifest.xml b/PrintApp/AndroidManifest.xml
index 7528f28..1442178 100644
--- a/PrintApp/AndroidManifest.xml
+++ b/PrintApp/AndroidManifest.xml
@@ -4,8 +4,8 @@
android:versionName="1.0" >
<uses-sdk
- android:minSdkVersion="KeyLimePie"
- android:targetSdkVersion="KeyLimePie" />
+ android:minSdkVersion="19"
+ android:targetSdkVersion="19" />
<application
android:icon="@drawable/ic_launcher"
diff --git a/PrintApp/res/menu/activity_main.xml b/PrintApp/res/menu/activity_main.xml
index 31bb883..060c09a 100644
--- a/PrintApp/res/menu/activity_main.xml
+++ b/PrintApp/res/menu/activity_main.xml
@@ -1,8 +1,4 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@+id/menu_settings"
- android:title="@string/menu_settings"
- android:orderInCategory="100"
- android:showAsAction="never" />
<item android:id="@+id/menu_print"
android:title="@string/print"
android:orderInCategory="100"
diff --git a/PrintApp/src/foo/bar/print/PrintActivity.java b/PrintApp/src/foo/bar/print/PrintActivity.java
index dfd339b..03c3b89 100644
--- a/PrintApp/src/foo/bar/print/PrintActivity.java
+++ b/PrintApp/src/foo/bar/print/PrintActivity.java
@@ -16,31 +16,30 @@
package foo.bar.print;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
import android.app.Activity;
import android.content.Context;
+import android.graphics.pdf.PdfDocument.Page;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
+import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
-import android.print.PrintJob;
import android.print.PrintManager;
-import android.print.pdf.PdfDocument.Page;
-import android.util.Log;
+import android.print.pdf.PrintedPdfDocument;
+import android.util.SparseIntArray;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.util.List;
-
-import libcore.io.IoUtils;
-
/**
* Simple sample of how to use the print APIs.
*/
@@ -48,6 +47,8 @@ public class PrintActivity extends Activity {
public static final String LOG_TAG = "PrintActivity";
+ private static final int PAGE_COUNT = 5;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -63,106 +64,213 @@ public class PrintActivity extends Activity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- if (!super.onOptionsItemSelected(item)) {
- if (item.getItemId() == R.id.menu_print) {
- printView();
- return true;
- }
- }
- return false;
- }
-
- public void printFileSimple() {
- PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
-
- PrintJob printJob = printManager.print("My Print Job", new File("foo.pdf"), null);
-
- if (printJob != null) {
- /* Yay, we scheduled something to be printed!!! */
+ if (item.getItemId() == R.id.menu_print) {
+ printView();
+ return true;
}
+ return super.onOptionsItemSelected(item);
}
private void printView() {
PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
-
final View view = findViewById(R.id.content);
- PrintJob printJob = printManager.print("Print_View",
+ printManager.print("Print_View",
new PrintDocumentAdapter() {
- private PrintedPdfDocument mPdfDocument;
+ private static final int RESULT_LAYOUT_FAILED = 1;
+ private static final int RESULT_LAYOUT_FINISHED = 2;
- @Override
- public void onStart() {
- Log.i(LOG_TAG, "onStart");
- super.onStart();
- }
+ private PrintAttributes mPrintAttributes;
@Override
- public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
- CancellationSignal cancellationSignal, LayoutResultCallback callback,
- Bundle metadata) {
- Log.i(LOG_TAG, "onLayout");
-
- mPdfDocument = new PrintedPdfDocument(PrintActivity.this, newAttributes);
- Page page = mPdfDocument.startPage();
- view.draw(page.getCanvas());
- mPdfDocument.finishPage(page);
-
- PrintDocumentInfo info = new PrintDocumentInfo.Builder()
- .setPageCount(1)
- .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
- .create();
- callback.onLayoutFinished(info, false);
+ public void onLayout(final PrintAttributes oldAttributes,
+ final PrintAttributes newAttributes,
+ final CancellationSignal cancellationSignal,
+ final LayoutResultCallback callback,
+ final Bundle metadata) {
+
+ new AsyncTask<Void, Void, Integer>() {
+ @Override
+ protected void onPreExecute() {
+ // First register for cancellation requests.
+ cancellationSignal.setOnCancelListener(new OnCancelListener() {
+ @Override
+ public void onCancel() {
+ cancel(true);
+ }
+ });
+ mPrintAttributes = newAttributes;
+ }
+
+ @Override
+ protected Integer doInBackground(Void... params) {
+ try {
+ // Pretend we do some layout work.
+ for (int i = 0; i < PAGE_COUNT; i++) {
+ // Be nice and respond to cancellation.
+ if (isCancelled()) {
+ return null;
+ }
+ pretendDoingLayoutWork();
+ }
+ return RESULT_LAYOUT_FINISHED;
+ } catch (Exception e) {
+ return RESULT_LAYOUT_FAILED;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ // The task was not cancelled, so handle the layout result.
+ switch (result) {
+ case RESULT_LAYOUT_FINISHED: {
+ PrintDocumentInfo info = new PrintDocumentInfo
+ .Builder("print_view.pdf")
+ .setContentType(PrintDocumentInfo
+ .CONTENT_TYPE_DOCUMENT)
+ .setPageCount(PAGE_COUNT)
+ .build();
+ callback.onLayoutFinished(info, false);
+ } break;
+
+ case RESULT_LAYOUT_FAILED: {
+ callback.onLayoutFailed(null);
+ } break;
+ }
+ }
+
+ @Override
+ protected void onCancelled(Integer result) {
+ // Task was cancelled, report that.
+ callback.onLayoutCancelled();
+ }
+
+ private void pretendDoingLayoutWork() throws Exception {
+
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
@Override
- public void onWrite(final List<PageRange> pages,
- final FileDescriptor destination,
+ public void onWrite(final PageRange[] pages,
+ final ParcelFileDescriptor destination,
final CancellationSignal canclleationSignal,
final WriteResultCallback callback) {
- Log.i(LOG_TAG, "onWrite");
- final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+
+ new AsyncTask<Void, Void, Integer>() {
+ private static final int RESULT_WRITE_FAILED = 1;
+ private static final int RESULT_WRITE_FINISHED = 2;
+
+ private final SparseIntArray mWrittenPages = new SparseIntArray();
+ private final PrintedPdfDocument mPdfDocument = new PrintedPdfDocument(
+ PrintActivity.this, mPrintAttributes);
+
@Override
- protected Void doInBackground(Void... params) {
- try {
- mPdfDocument.writeTo(new FileOutputStream(destination));
- } finally {
- mPdfDocument.close();
- IoUtils.closeQuietly(destination);
+ protected void onPreExecute() {
+ // First register for cancellation requests.
+ canclleationSignal.setOnCancelListener(new OnCancelListener() {
+ @Override
+ public void onCancel() {
+ cancel(true);
+ }
+ });
+
+ for (int i = 0; i < PAGE_COUNT; i++) {
+ // Be nice and respond to cancellation.
+ if (isCancelled()) {
+ return;
+ }
+
+ // Write the page only if it was requested.
+ if (containsPage(pages, i)) {
+ mWrittenPages.append(mWrittenPages.size(), i);
+ Page page = mPdfDocument.startPage(i);
+ // The page of the PDF backed canvas size is in pixels (1/72") and
+ // smaller that the view. We scale down the drawn content and to
+ // fit. This does not lead to losing data as PDF is a vector format.
+ final float scale = (float) Math.min(mPdfDocument.getPageWidth(),
+ mPdfDocument.getPageHeight()) / Math.max(view.getWidth(), view.getHeight());
+ page.getCanvas().scale(scale, scale);
+ view.draw(page.getCanvas());
+ mPdfDocument.finishPage(page);
+ }
}
- return null;
}
@Override
- protected void onPostExecute(Void result) {
- callback.onWriteFinished(pages);
+ protected Integer doInBackground(Void... params) {
+ // Write the data and return success or failure.
+ try {
+ mPdfDocument.writeTo(new FileOutputStream(
+ destination.getFileDescriptor()));
+ return RESULT_WRITE_FINISHED;
+ } catch (IOException ioe) {
+ return RESULT_WRITE_FAILED;
+ }
}
@Override
- protected void onCancelled(Void result) {
- callback.onWriteFailed("Cancelled");
+ protected void onPostExecute(Integer result) {
+ // The task was not cancelled, so handle the write result.
+ switch (result) {
+ case RESULT_WRITE_FINISHED: {
+ PageRange[] pageRanges = computePageRanges(mWrittenPages);
+ callback.onWriteFinished(pageRanges);
+ } break;
+
+ case RESULT_WRITE_FAILED: {
+ callback.onWriteFailed(null);
+ } break;
+ }
+
+ mPdfDocument.close();
}
- };
-
- canclleationSignal.setOnCancelListener(new OnCancelListener() {
+
@Override
- public void onCancel() {
- task.cancel(true);
+ protected void onCancelled(Integer result) {
+ // Task was cancelled, report that.
+ callback.onWriteCancelled();
+ mPdfDocument.close();
}
- });
-
- task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
- @Override
- public void onFinish() {
- Log.i(LOG_TAG, "onFinish");
- super.onFinish();
+ private PageRange[] computePageRanges(SparseIntArray writtenPages) {
+ List<PageRange> pageRanges = new ArrayList<PageRange>();
+
+ int start = -1;
+ int end = -1;
+ final int writtenPageCount = writtenPages.size();
+ for (int i = 0; i < writtenPageCount; i++) {
+ if (start < 0) {
+ start = writtenPages.valueAt(i);
+ }
+ int oldEnd = end = start;
+ while (i < writtenPageCount && (end - oldEnd) <= 1) {
+ oldEnd = end;
+ end = writtenPages.valueAt(i);
+ i++;
+ }
+ PageRange pageRange = new PageRange(start, end);
+ pageRanges.add(pageRange);
+ start = end = -1;
+ }
+
+ PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
+ pageRanges.toArray(pageRangesArray);
+ return pageRangesArray;
}
- }, new PrintAttributes.Builder().create());
- if (printJob != null) {
- /* Yay, we scheduled something to be printed!!! */
- }
+ private boolean containsPage(PageRange[] pageRanges, int page) {
+ final int pageRangeCount = pageRanges.length;
+ for (int i = 0; i < pageRangeCount; i++) {
+ if (pageRanges[i].getStart() <= page
+ && pageRanges[i].getEnd() >= page) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }, null);
}
}
diff --git a/PrintApp/src/foo/bar/print/PrintedPdfDocument.java b/PrintApp/src/foo/bar/print/PrintedPdfDocument.java
deleted file mode 100644
index f6cdb4a..0000000
--- a/PrintApp/src/foo/bar/print/PrintedPdfDocument.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package foo.bar.print;
-
-import android.content.Context;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
-import android.print.PrintAttributes;
-import android.print.PrintAttributes.Margins;
-import android.print.PrintAttributes.MediaSize;
-import android.print.PrintAttributes.Resolution;
-import android.print.pdf.PdfDocument;
-import android.print.pdf.PdfDocument.Page;
-import android.print.pdf.PdfDocument.PageInfo;
-import android.util.DisplayMetrics;
-import android.view.Display;
-
-import java.io.OutputStream;
-
-/**
- * This class is a helper for printing content to a different media
- * size. The printed content will be as large as it would be on the
- * screen.
- *
- * THIS IS A TEST CODE AND IS HIDDEN
- *
- * @hide
- */
-public final class PrintedPdfDocument {
- private static final int MILS_PER_INCH = 1000;
-
- /**
- * Maximum bitmap size as defined in Skia's native code (see SkCanvas.cpp,
- * SkDraw.cpp)
- */
- private static final int MAXMIMUM_BITMAP_SIZE = 32766;
-
- private final PdfDocument mDocument = PdfDocument.open();
- private final Rect mPageSize = new Rect();
- private final Rect mContentSize = new Rect();
-
- private final int mCanvasDensityDpi;
- private final Matrix mContentToPageTransform;
-
- private Matrix mTempMatrix;
-
- /**
- * Creates a new instance.
- *
- * @param context Context instance for accessing resources and services.
- * @param attributes The {@link PrintAttributes} to user.
- */
- public PrintedPdfDocument(Context context, PrintAttributes attributes) {
- MediaSize mediaSize = attributes.getMediaSize();
- Resolution resolution = attributes.getResolution();
-
- // TODO: What to do if horizontal and vertical DPI differ?
- mCanvasDensityDpi = Math.max(attributes.getResolution().getHorizontalDpi(), attributes
- .getResolution().getVerticalDpi());
-
- // Figure out the scale since the content and the target DPI may differ.
- DisplayMetrics metrics = new DisplayMetrics();
- DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
- dm.getDisplay(Display.DEFAULT_DISPLAY).getMetrics(metrics);
- final float scaleFactor = ((float) mCanvasDensityDpi) / metrics.densityDpi;
- mContentToPageTransform = new Matrix();
- mContentToPageTransform.setScale(scaleFactor, scaleFactor);
-
- // Compute the size of the target canvas from the attributes.
- final int pageWidth = (mediaSize.getWidthMils() / MILS_PER_INCH)
- * resolution.getHorizontalDpi();
- final int pageHeight = (mediaSize.getHeightMils() / MILS_PER_INCH)
- * resolution.getVerticalDpi();
- mPageSize.set(0, 0, pageWidth, pageHeight);
-
- // Compute the content size from the attributes.
- Margins margins = attributes.getMargins();
- final int marginLeft = (margins.getLeftMils() / MILS_PER_INCH)
- * resolution.getHorizontalDpi();
- final int marginTop = (margins.getTopMils() / MILS_PER_INCH) * resolution.getVerticalDpi();
- final int marginRight = (margins.getRightMils() / MILS_PER_INCH)
- * resolution.getHorizontalDpi();
- final int marginBottom = (margins.getBottomMils() / MILS_PER_INCH)
- * resolution.getVerticalDpi();
- mContentSize.set(mPageSize.left + marginLeft, mPageSize.top + marginTop, mPageSize.right
- - marginRight, mPageSize.bottom - marginBottom);
- }
-
- /**
- * Starts a new page.
- *
- * @return The started page.
- */
- public Page startPage() {
- return startPage(null);
- }
-
- /**
- * Starts a new page.
- *
- * @param additionalTransform Additional initial transform.
- * @return The started page.
- */
- public Page startPage(Matrix additionalTransform) {
- Matrix transform = null;
- if (additionalTransform != null) {
- if (mTempMatrix == null) {
- transform = mTempMatrix = new Matrix();
- }
- transform.set(mContentToPageTransform);
- transform.postConcat(additionalTransform);
- } else {
- transform = mContentToPageTransform;
- }
- PageInfo pageInfo = new PageInfo.Builder(mPageSize, 0, mCanvasDensityDpi).create();
- Page page = mDocument.startPage(pageInfo);
- return page;
- }
-
- /**
- * Finishes a page.
- *
- * @param page The page to finish.
- */
- public void finishPage(Page page) {
- mDocument.finishPage(page);
- }
-
- /**
- * Writes the document to a stream.
- *
- * @param out The destination stream.
- */
- public void writeTo(OutputStream out) {
- mDocument.writeTo(out);
- }
-
- /**
- * Closes the document.
- */
- public void close() {
- mDocument.close();
- }
-}
diff --git a/PrintService/Android.mk b/PrintService/Android.mk
index 7c5d5fa..6d4ff7f 100644
--- a/PrintService/Android.mk
+++ b/PrintService/Android.mk
@@ -8,6 +8,4 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := PrintService
-LOCAL_CERTIFICATE := platform
-
include $(BUILD_PACKAGE)
diff --git a/PrintService/AndroidManifest.xml b/PrintService/AndroidManifest.xml
index 096e7cd..6b6dd5c 100644
--- a/PrintService/AndroidManifest.xml
+++ b/PrintService/AndroidManifest.xml
@@ -4,8 +4,8 @@
android:versionName="1.0" >
<uses-sdk
- android:minSdkVersion="KeyLimePie"
- android:targetSdkVersion="KeyLimePie" />
+ android:minSdkVersion="19"
+ android:targetSdkVersion="19" />
<application
android:icon="@drawable/ic_launcher"
@@ -25,13 +25,24 @@
<activity
android:name=".SettingsActivity"
android:label="@string/settings_activity_label"
- android:permission="android.permission.BIND_PRINT_SERVICE">
+ >
</activity>
<activity
android:name=".AddPrintersActivity"
- android:label="@string/add_pritners_activity_label"
- android:permission="android.permission.BIND_PRINT_SERVICE">
+ android:label="@string/add_pritners_activity_label">
+ </activity>
+
+ <activity
+ android:name=".MyDialogActivity"
+ android:exported="false">
</activity>
+
+ <activity
+ android:name=".CustomPrintOptionsActivity"
+ android:exported="true"
+ android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY">
+ </activity>
+
</application>
</manifest>
diff --git a/PrintService/res/values/arrays.xml b/PrintService/res/values/arrays.xml
new file mode 100644
index 0000000..02cb0ad
--- /dev/null
+++ b/PrintService/res/values/arrays.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<resources>
+
+ <string-array name="on_print_job_queued_actions">
+ <item>@string/print_now</item>
+ <item>@string/print_delayed</item>
+ <item>@string/fail_now</item>
+ <item>@string/fail_delayed</item>
+ <item>@string/block_now</item>
+ <item>@string/block_delayed</item>
+ <item>@string/block_and_delayed_unblock</item>
+ </string-array>
+
+ <string-array name="on_request_cancel_print_job_actions">
+ <item>@string/cancel_yes</item>
+ <item>@string/cancel_no</item>
+ </string-array>
+
+</resources>
diff --git a/PrintService/res/values/strings.xml b/PrintService/res/values/strings.xml
index 7a4b0b1..271639a 100644
--- a/PrintService/res/values/strings.xml
+++ b/PrintService/res/values/strings.xml
@@ -15,4 +15,19 @@
<string name="output_tray_first">First output tray</string>
<string name="output_tray_second">Second output tray</string>
+ <string name="print_now">Print now</string>
+ <string name="print_delayed">Print dealyed</string>
+ <string name="fail_now">Fail now</string>
+ <string name="fail_delayed">Fail delayed</string>
+ <string name="block_now">Block now</string>
+ <string name="block_delayed">Block delayed</string>
+ <string name="block_and_delayed_unblock">Block and delayed unblock</string>
+ <string name="cancel_yes">Cancel</string>
+ <string name="cancel_no">Don\'t cancel</string>
+
+ <string name="fail_reason">Don\'t feel like doing it</string>
+
+ <string name="on_print_job_pending_activity_title">New print job pending. What to do?</string>
+ <string name="on_cancle_print_job_requested_activity_title">Print job cancel reqeusted. What to do?</string>
+
</resources>
diff --git a/PrintService/res/xml/printservice.xml b/PrintService/res/xml/printservice.xml
index 667bef7..690a545 100644
--- a/PrintService/res/xml/printservice.xml
+++ b/PrintService/res/xml/printservice.xml
@@ -1,3 +1,4 @@
<print-service xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="foo.bar.printservice.SettingsActivity"
- android:addPrintersActivity="foo.bar.printservice.AddPrintersActivity"/>
+ android:addPrintersActivity="foo.bar.printservice.AddPrintersActivity"
+ android:advancedPrintOptionsActivity="foo.bar.printservice.CustomPrintOptionsActivity"/>
diff --git a/PrintService/src/foo/bar/printservice/CustomPrintOptionsActivity.java b/PrintService/src/foo/bar/printservice/CustomPrintOptionsActivity.java
new file mode 100644
index 0000000..67eda45
--- /dev/null
+++ b/PrintService/src/foo/bar/printservice/CustomPrintOptionsActivity.java
@@ -0,0 +1,55 @@
+package foo.bar.printservice;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintJobInfo;
+import android.print.PrinterCapabilitiesInfo;
+import android.print.PrinterInfo;
+import android.printservice.PrintService;
+
+public class CustomPrintOptionsActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ PrintJobInfo printJobInfo = (PrintJobInfo) getIntent().getParcelableExtra(
+ PrintService.EXTRA_PRINT_JOB_INFO);
+ PrinterInfo printerInfo = (PrinterInfo) getIntent().getParcelableExtra(
+ "android.intent.extra.print.EXTRA_PRINTER_INFO");
+
+ PrinterCapabilitiesInfo capabilities = printerInfo.getCapabilities();
+
+ PrintAttributes attributes = new PrintAttributes.Builder()
+ .setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME)
+ .setMediaSize(MediaSize.ISO_A5)
+ .setResolution(capabilities.getResolutions().get(0))
+ .build();
+
+ PrintJobInfo.Builder builder = new PrintJobInfo.Builder(printJobInfo);
+ builder.setAttributes(attributes);
+ builder.setCopies(2);
+ builder.setAttributes(attributes);
+ builder.setPages(new PageRange[] {new PageRange(1, 1), new PageRange(3, 3)});
+ builder.putAdvancedOption("EXTRA_FIRST_ADVANCED_OPTION", "OPALA");
+ builder.putAdvancedOption("EXTRA_SECOND_ADVANCED_OPTION", 1);
+
+ PrintJobInfo newPrintJobInfo = builder.build();
+
+ Intent result = new Intent();
+ result.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, newPrintJobInfo);
+ setResult(Activity.RESULT_OK, result);
+
+ finish();
+ }
+}
diff --git a/PrintService/src/foo/bar/printservice/MyDialogActivity.java b/PrintService/src/foo/bar/printservice/MyDialogActivity.java
new file mode 100644
index 0000000..40a2f27
--- /dev/null
+++ b/PrintService/src/foo/bar/printservice/MyDialogActivity.java
@@ -0,0 +1,112 @@
+package foo.bar.printservice;
+
+import android.app.ListActivity;
+import android.os.Bundle;
+import android.print.PrintJobId;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+
+public class MyDialogActivity extends ListActivity {
+
+ private static final int ITEM_INDEX_PRINT_NOW = 0;
+ private static final int ITEM_INDEX_PRINT_DELAYED = 1;
+ private static final int ITEM_INDEX_FAIL_NOW = 2;
+ private static final int ITEM_INDEX_FAIL_DELAYED = 3;
+ private static final int ITEM_INDEX_BLOCK_NOW = 4;
+ private static final int ITEM_INDEX_BLOCK_DELAYED = 5;
+ private static final int ITEM_INDEX_BLOCK_AND_DELAYED_UNBLOCK = 6;
+
+ private static final int ITEM_INDEX_CANCEL_YES = 0;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ PrintJobId printJobId = getIntent().getParcelableExtra(
+ MyPrintService.INTENT_EXTRA_PRINT_JOB_ID);
+ final int actionType = getIntent().getIntExtra(MyPrintService.INTENT_EXTRA_ACTION_TYPE,
+ MyPrintService.ACTION_TYPE_ON_PRINT_JOB_PENDING);
+
+ if (actionType == MyPrintService.ACTION_TYPE_ON_PRINT_JOB_PENDING) {
+ createActionTypeOnPrintJobPendingUi(printJobId);
+ } else {
+ createActionTypeOnReqeustCancelPrintJobUi(printJobId);
+ }
+ }
+
+ private void createActionTypeOnPrintJobPendingUi(final PrintJobId printJobId) {
+ setTitle(getString(R.string.on_print_job_pending_activity_title));
+
+ setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
+ getResources().getStringArray(R.array.on_print_job_queued_actions)));
+ getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ switch (position) {
+ case ITEM_INDEX_PRINT_NOW: {
+ MyPrintService.peekInstance().handleQueuedPrintJob(printJobId);
+ finish();
+ } break;
+
+ case ITEM_INDEX_PRINT_DELAYED: {
+ MyPrintService.peekInstance().handleQueuedPrintJobDelayed(printJobId);
+ finish();
+ } break;
+
+ case ITEM_INDEX_FAIL_NOW: {
+ MyPrintService.peekInstance().handleFailPrintJob(printJobId);
+ finish();
+ } break;
+
+ case ITEM_INDEX_FAIL_DELAYED: {
+ MyPrintService.peekInstance().handleFailPrintJobDelayed(printJobId);
+ finish();
+ } break;
+
+ case ITEM_INDEX_BLOCK_NOW: {
+ MyPrintService.peekInstance().handleBlockPrintJob(printJobId);
+ finish();
+ } break;
+
+ case ITEM_INDEX_BLOCK_DELAYED: {
+ MyPrintService.peekInstance().handleBlockPrintJobDelayed(printJobId);
+ finish();
+ } break;
+
+ case ITEM_INDEX_BLOCK_AND_DELAYED_UNBLOCK: {
+ MyPrintService.peekInstance().handleBlockAndDelayedUnblockPrintJob(
+ printJobId);
+ finish();
+ } break;
+
+ default: {
+ finish();
+ } break;
+ }
+ }
+ });
+ }
+
+ private void createActionTypeOnReqeustCancelPrintJobUi(final PrintJobId printJobId) {
+ setTitle(getString(R.string.on_cancle_print_job_requested_activity_title));
+
+ setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
+ getResources().getStringArray(R.array.on_request_cancel_print_job_actions)));
+ getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ switch (position) {
+ case ITEM_INDEX_CANCEL_YES: {
+ MyPrintService.peekInstance().handleRequestCancelPrintJob(printJobId);
+ finish();
+ } break;
+
+ default: {
+ finish();
+ } break;
+ }
+ }
+ });
+ }
+}
diff --git a/PrintService/src/foo/bar/printservice/MyPrintService.java b/PrintService/src/foo/bar/printservice/MyPrintService.java
index a1f809c..f04d86c 100644
--- a/PrintService/src/foo/bar/printservice/MyPrintService.java
+++ b/PrintService/src/foo/bar/printservice/MyPrintService.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package foo.bar.printservice;
import android.content.Intent;
@@ -6,18 +22,21 @@ import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.print.PrintAttributes;
import android.print.PrintAttributes.Margins;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Resolution;
-import android.print.PrintAttributes.Tray;
+import android.print.PrintJobId;
import android.print.PrintJobInfo;
+import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.printservice.PrintJob;
import android.printservice.PrintService;
+import android.printservice.PrinterDiscoverySession;
+import android.util.ArrayMap;
import android.util.Log;
-import android.widget.Toast;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -29,59 +48,197 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
public class MyPrintService extends PrintService {
- private static final String LOG_TAG = MyPrintService.class.getSimpleName();
+ private static final String LOG_TAG = "MyPrintService";
+
+ private static final long STANDARD_DELAY_MILLIS = 10000000;
+
+ static final String INTENT_EXTRA_ACTION_TYPE = "INTENT_EXTRA_ACTION_TYPE";
+ static final String INTENT_EXTRA_PRINT_JOB_ID = "INTENT_EXTRA_PRINT_JOB_ID";
+
+ static final int ACTION_TYPE_ON_PRINT_JOB_PENDING = 1;
+ static final int ACTION_TYPE_ON_REQUEST_CANCEL_PRINT_JOB = 2;
+
+ private static final Object sLock = new Object();
+
+ private static MyPrintService sInstance;
private Handler mHandler;
+ private AsyncTask<ParcelFileDescriptor, Void, Void> mFakePrintTask;
+
+ private FakePrinterDiscoverySession mSession;
+
+ private final Map<PrintJobId, PrintJob> mProcessedPrintJobs =
+ new ArrayMap<PrintJobId, PrintJob>();
+
+ public static MyPrintService peekInstance() {
+ synchronized (sLock) {
+ return sInstance;
+ }
+ }
+
@Override
protected void onConnected() {
- mHandler = new MyHandler(getMainLooper());
Log.i(LOG_TAG, "#onConnected()");
+ mHandler = new MyHandler(getMainLooper());
+ synchronized (sLock) {
+ sInstance = this;
+ }
}
@Override
protected void onDisconnected() {
- cancellAddingFakePrinters();
Log.i(LOG_TAG, "#onDisconnected()");
+ if (mSession != null) {
+ mSession.cancellAddingFakePrinters();
+ }
+ synchronized (sLock) {
+ sInstance = null;
+ }
}
@Override
- protected void onStartPrinterDiscovery() {
- Log.i(LOG_TAG, "#onStartDiscoverPrinters()");
- Message message1 = mHandler.obtainMessage(MyHandler.MESSAGE_ADD_FIRST_FAKE_PRINTER);
- mHandler.sendMessageDelayed(message1, 0);
-
- Message message2 = mHandler.obtainMessage(MyHandler.MESSAGE_ADD_SECOND_FAKE_PRINTER);
- mHandler.sendMessageDelayed(message2, 10000);
+ protected PrinterDiscoverySession onCreatePrinterDiscoverySession() {
+ Log.i(LOG_TAG, "#onCreatePrinterDiscoverySession()");
+ return new FakePrinterDiscoverySession();
}
@Override
- protected void onStopPrinterDiscovery() {
- cancellAddingFakePrinters();
- Log.i(LOG_TAG, "#onStopDiscoverPrinters()");
+ protected void onRequestCancelPrintJob(final PrintJob printJob) {
+ Log.i(LOG_TAG, "#onRequestCancelPrintJob()");
+ mProcessedPrintJobs.put(printJob.getId(), printJob);
+ Intent intent = new Intent(this, MyDialogActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(INTENT_EXTRA_PRINT_JOB_ID, printJob.getId());
+ intent.putExtra(INTENT_EXTRA_ACTION_TYPE, ACTION_TYPE_ON_REQUEST_CANCEL_PRINT_JOB);
+ startActivity(intent);
}
@Override
public void onPrintJobQueued(final PrintJob printJob) {
Log.i(LOG_TAG, "#onPrintJobQueued()");
- PrintJobInfo info = printJob.getInfo();
- final File file = new File(getFilesDir(), info.getLabel() + ".pdf");
- if (file.exists()) {
- file.delete();
+ mProcessedPrintJobs.put(printJob.getId(), printJob);
+ if (printJob.isQueued()) {
+ printJob.start();
+ }
+
+ Intent intent = new Intent(this, MyDialogActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(INTENT_EXTRA_PRINT_JOB_ID, printJob.getId());
+ intent.putExtra(INTENT_EXTRA_ACTION_TYPE, ACTION_TYPE_ON_PRINT_JOB_PENDING);
+ startActivity(intent);
+ }
+
+ void handleRequestCancelPrintJob(PrintJobId printJobId) {
+ PrintJob printJob = mProcessedPrintJobs.get(printJobId);
+ if (printJob == null) {
+ return;
+ }
+ mProcessedPrintJobs.remove(printJobId);
+ if (printJob.isQueued() || printJob.isStarted() || printJob.isBlocked()) {
+ mHandler.removeMessages(MyHandler.MSG_HANDLE_DO_PRINT_JOB);
+ mHandler.removeMessages(MyHandler.MSG_HANDLE_FAIL_PRINT_JOB);
+ printJob.cancel();
+ }
+ }
+
+ void handleFailPrintJobDelayed(PrintJobId printJobId) {
+ Message message = mHandler.obtainMessage(
+ MyHandler.MSG_HANDLE_FAIL_PRINT_JOB, printJobId);
+ mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS);
+ }
+
+ void handleFailPrintJob(PrintJobId printJobId) {
+ PrintJob printJob = mProcessedPrintJobs.get(printJobId);
+ if (printJob == null) {
+ return;
}
- AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+ mProcessedPrintJobs.remove(printJobId);
+ if (printJob.isQueued() || printJob.isStarted()) {
+ printJob.fail(getString(R.string.fail_reason));
+ }
+ }
+
+ void handleBlockPrintJobDelayed(PrintJobId printJobId) {
+ Message message = mHandler.obtainMessage(
+ MyHandler.MSG_HANDLE_BLOCK_PRINT_JOB, printJobId);
+ mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS);
+ }
+
+ void handleBlockPrintJob(PrintJobId printJobId) {
+ final PrintJob printJob = mProcessedPrintJobs.get(printJobId);
+ if (printJob == null) {
+ return;
+ }
+
+ if (printJob.isStarted()) {
+ printJob.block("Gimme some rest, dude");
+ }
+ }
+
+ void handleBlockAndDelayedUnblockPrintJob(PrintJobId printJobId) {
+ handleBlockPrintJob(printJobId);
+
+ Message message = mHandler.obtainMessage(
+ MyHandler.MSG_HANDLE_UNBLOCK_PRINT_JOB, printJobId);
+ mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS);
+ }
+
+ void handleUnblockPrintJob(PrintJobId printJobId) {
+ final PrintJob printJob = mProcessedPrintJobs.get(printJobId);
+ if (printJob == null) {
+ return;
+ }
+
+ if (printJob.isBlocked()) {
+ printJob.start();
+ }
+ }
+
+ void handleQueuedPrintJobDelayed(PrintJobId printJobId) {
+ final PrintJob printJob = mProcessedPrintJobs.get(printJobId);
+ if (printJob == null) {
+ return;
+ }
+
+ if (printJob.isQueued()) {
+ printJob.start();
+ }
+ Message message = mHandler.obtainMessage(
+ MyHandler.MSG_HANDLE_DO_PRINT_JOB, printJobId);
+ mHandler.sendMessageDelayed(message, STANDARD_DELAY_MILLIS);
+ }
+
+ void handleQueuedPrintJob(PrintJobId printJobId) {
+ final PrintJob printJob = mProcessedPrintJobs.get(printJobId);
+ if (printJob == null) {
+ return;
+ }
+
+ if (printJob.isQueued()) {
+ printJob.start();
+ }
+
+ final PrintJobInfo info = printJob.getInfo();
+ final File file = new File(getFilesDir(), info.getLabel() + ".pdf");
+
+ mFakePrintTask = new AsyncTask<ParcelFileDescriptor, Void, Void>() {
@Override
- protected Void doInBackground(Void... params) {
- InputStream in = new BufferedInputStream(
- new FileInputStream(printJob.getDocument().getData()));
+ protected Void doInBackground(ParcelFileDescriptor... params) {
+ InputStream in = new BufferedInputStream(new FileInputStream(
+ params[0].getFileDescriptor()));
OutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(file));
final byte[] buffer = new byte[8192];
while (true) {
+ if (isCancelled()) {
+ break;
+ }
final int readByteCount = in.read(buffer);
if (readByteCount < 0) {
break;
@@ -89,155 +246,227 @@ public class MyPrintService extends PrintService {
out.write(buffer, 0, readByteCount);
}
} catch (IOException ioe) {
- /* ignore */
+ throw new RuntimeException(ioe);
} finally {
- try {
- in.close();
- } catch (IOException ioe) {
- /* ignore */
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException ioe) {
+ /* ignore */
+ }
}
if (out != null) {
try {
out.close();
} catch (IOException ioe) {
- /* ignore */
+ /* ignore */
}
}
+ if (isCancelled()) {
+ file.delete();
+ }
}
return null;
}
@Override
protected void onPostExecute(Void result) {
- file.setExecutable(true, false);
- file.setWritable(true, false);
+ if (printJob.isStarted()) {
+ printJob.complete();
+ }
+
file.setReadable(true, false);
+ // Quick and dirty to show the file - use a content provider instead.
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/pdf");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent, null);
- if (printJob.isQueued()) {
- printJob.start();
- }
-
- PrintJobInfo info = printJob.getInfo();
- Toast.makeText(MyPrintService.this,
- "Printer: " + info.getPrinterId().getLocalId()
- + " copies: " + info.getAttributes().getCopies(),
- Toast.LENGTH_SHORT).show();
+ mFakePrintTask = null;
+ }
+ @Override
+ protected void onCancelled(Void result) {
if (printJob.isStarted()) {
- printJob.complete();
+ printJob.cancel();
}
}
};
- task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
-
- private void addFirstFakePrinter() {
- PrinterId printerId = generatePrinterId("1");
- PrinterInfo printer = new PrinterInfo.Builder(printerId, "Printer 1")
- .setStatus(PrinterInfo.STATUS_READY)
- .setMinMargins(new Margins(0, 0, 0, 0), new Margins(0, 0, 0, 0))
- .addMediaSize(MediaSize.createMediaSize(getPackageManager(),
- MediaSize.ISO_A2), true)
- .addMediaSize(MediaSize.createMediaSize(getPackageManager(),
- MediaSize.ISO_A3), false)
- .addMediaSize(MediaSize.createMediaSize(getPackageManager(),
- MediaSize.ISO_A4), false)
- .addMediaSize(MediaSize.createMediaSize(getPackageManager(),
- MediaSize.NA_LETTER), false)
- .addResolution(new Resolution("R1", getString(
- R.string.resolution_600x600), 600, 600), true)
- .addInputTray(new Tray("FirstInputTray", getString(
- R.string.input_tray_first)), false)
- .addOutputTray(new Tray("FirstOutputTray", getString(
- R.string.output_tray_first)), false)
- .setDuplexModes(PrintAttributes.DUPLEX_MODE_NONE
- | PrintAttributes.DUPLEX_MODE_LONG_EDGE
- | PrintAttributes.DUPLEX_MODE_SHORT_EDGE,
- PrintAttributes.DUPLEX_MODE_NONE)
- .setColorModes(PrintAttributes.COLOR_MODE_COLOR
- | PrintAttributes.COLOR_MODE_MONOCHROME,
- PrintAttributes.COLOR_MODE_COLOR)
- .setFittingModes(PrintAttributes.FITTING_MODE_NONE
- | PrintAttributes.FITTING_MODE_FIT_TO_PAGE,
- PrintAttributes.FITTING_MODE_NONE)
- .setOrientations(PrintAttributes.ORIENTATION_PORTRAIT
- | PrintAttributes.ORIENTATION_LANDSCAPE,
- PrintAttributes.ORIENTATION_PORTRAIT)
- .create();
- List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
- printers.add(printer);
- addDiscoveredPrinters(printers);
- }
-
- private void addSecondFakePrinter() {
- PrinterId printerId = generatePrinterId("2");
- PrinterInfo printer = new PrinterInfo.Builder(printerId, "Printer 2")
- .setStatus(PrinterInfo.STATUS_READY)
- .setMinMargins(new Margins(0, 0, 0, 0), new Margins(0, 0, 0, 0))
- .addMediaSize(MediaSize.createMediaSize(getPackageManager(),
- MediaSize.ISO_A4), true)
- .addMediaSize(MediaSize.createMediaSize(getPackageManager(),
- MediaSize.ISO_A5), false)
- .addResolution(new Resolution("R1", getString(
- R.string.resolution_200x200), 200, 200), true)
- .addResolution(new Resolution("R2", getString(
- R.string.resolution_300x300), 300, 300), false)
- .addInputTray(new Tray("FirstInputTray", getString(
- R.string.input_tray_first)), false)
- .addInputTray(new Tray("SecondInputTray", getString(
- R.string.input_tray_second)), true)
- .addOutputTray(new Tray("FirstOutputTray", getString(
- R.string.output_tray_first)), false)
- .addOutputTray(new Tray("SecondOutputTray", getString(
- R.string.output_tray_second)), true)
- .setDuplexModes(PrintAttributes.DUPLEX_MODE_NONE
- | PrintAttributes.DUPLEX_MODE_LONG_EDGE
- | PrintAttributes.DUPLEX_MODE_SHORT_EDGE,
- PrintAttributes.DUPLEX_MODE_SHORT_EDGE)
- .setColorModes(PrintAttributes.COLOR_MODE_COLOR
- | PrintAttributes.COLOR_MODE_MONOCHROME,
- PrintAttributes.COLOR_MODE_MONOCHROME)
- .setFittingModes(PrintAttributes.FITTING_MODE_FIT_TO_PAGE
- | PrintAttributes.FITTING_MODE_NONE,
- PrintAttributes.FITTING_MODE_FIT_TO_PAGE)
- .setOrientations(PrintAttributes.ORIENTATION_PORTRAIT
- | PrintAttributes.ORIENTATION_LANDSCAPE,
- PrintAttributes.ORIENTATION_LANDSCAPE)
- .create();
- List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
- printers.add(printer);
- addDiscoveredPrinters(printers);
- }
-
- private void cancellAddingFakePrinters() {
- mHandler.removeMessages(MyHandler.MESSAGE_ADD_FIRST_FAKE_PRINTER);
- mHandler.removeMessages(MyHandler.MESSAGE_ADD_FIRST_FAKE_PRINTER);
+ mFakePrintTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
+ printJob.getDocument().getData());
}
private final class MyHandler extends Handler {
-
- public static final int MESSAGE_ADD_FIRST_FAKE_PRINTER = 1;
- public static final int MESSAGE_ADD_SECOND_FAKE_PRINTER = 2;
+ public static final int MSG_HANDLE_DO_PRINT_JOB = 1;
+ public static final int MSG_HANDLE_FAIL_PRINT_JOB = 2;
+ public static final int MSG_HANDLE_BLOCK_PRINT_JOB = 3;
+ public static final int MSG_HANDLE_UNBLOCK_PRINT_JOB = 4;
public MyHandler(Looper looper) {
- super(looper, null, true);
+ super(looper);
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
- case MESSAGE_ADD_FIRST_FAKE_PRINTER: {
- addFirstFakePrinter();
+ case MSG_HANDLE_DO_PRINT_JOB: {
+ PrintJobId printJobId = (PrintJobId) message.obj;
+ handleQueuedPrintJob(printJobId);
+ } break;
+
+ case MSG_HANDLE_FAIL_PRINT_JOB: {
+ PrintJobId printJobId = (PrintJobId) message.obj;
+ handleFailPrintJob(printJobId);
} break;
- case MESSAGE_ADD_SECOND_FAKE_PRINTER: {
- addSecondFakePrinter();
+
+ case MSG_HANDLE_BLOCK_PRINT_JOB: {
+ PrintJobId printJobId = (PrintJobId) message.obj;
+ handleBlockPrintJob(printJobId);
+ } break;
+
+ case MSG_HANDLE_UNBLOCK_PRINT_JOB: {
+ PrintJobId printJobId = (PrintJobId) message.obj;
+ handleUnblockPrintJob(printJobId);
} break;
}
}
}
+
+ private final class FakePrinterDiscoverySession extends PrinterDiscoverySession {
+ private final Handler mSesionHandler = new SessionHandler(getMainLooper());
+
+ private final List<PrinterInfo> mFakePrinters = new ArrayList<PrinterInfo>();
+
+ public FakePrinterDiscoverySession() {
+ for (int i = 0; i < 10; i++) {
+ String name = "Printer " + i;
+ PrinterInfo printer = new PrinterInfo
+ .Builder(generatePrinterId(name), name, (i % 2 == 1)
+ ? PrinterInfo.STATUS_UNAVAILABLE : PrinterInfo.STATUS_IDLE)
+ .build();
+ mFakePrinters.add(printer);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i(LOG_TAG, "FakePrinterDiscoverySession#onDestroy()");
+ mSesionHandler.removeMessages(SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS);
+ mSesionHandler.removeMessages(SessionHandler.MSG_ADD_SECOND_BATCH_FAKE_PRINTERS);
+ }
+
+ @Override
+ public void onStartPrinterDiscovery(List<PrinterId> priorityList) {
+ Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStartPrinterDiscovery()");
+ Message message1 = mSesionHandler.obtainMessage(
+ SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS, this);
+ mSesionHandler.sendMessageDelayed(message1, 0);
+
+ Message message2 = mSesionHandler.obtainMessage(
+ SessionHandler.MSG_ADD_SECOND_BATCH_FAKE_PRINTERS, this);
+ mSesionHandler.sendMessageDelayed(message2, 10000);
+ }
+
+ @Override
+ public void onStopPrinterDiscovery() {
+ Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStopPrinterDiscovery()");
+ cancellAddingFakePrinters();
+ }
+
+ @Override
+ public void onStartPrinterStateTracking(PrinterId printerId) {
+ Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStartPrinterStateTracking()");
+ PrinterInfo printer = findPrinterInfo(printerId);
+ if (printer != null) {
+ PrinterCapabilitiesInfo capabilities =
+ new PrinterCapabilitiesInfo.Builder(printerId)
+ .setMinMargins(new Margins(200, 200, 200, 200))
+ .addMediaSize(MediaSize.ISO_A4, true)
+ .addMediaSize(MediaSize.ISO_A5, false)
+ .addResolution(new Resolution("R1", getString(
+ R.string.resolution_200x200), 200, 200), false)
+ .addResolution(new Resolution("R2", getString(
+ R.string.resolution_300x300), 300, 300), true)
+ .setColorModes(PrintAttributes.COLOR_MODE_COLOR
+ | PrintAttributes.COLOR_MODE_MONOCHROME,
+ PrintAttributes.COLOR_MODE_MONOCHROME)
+ .build();
+
+ printer = new PrinterInfo.Builder(printer)
+ .setCapabilities(capabilities)
+ .build();
+
+ List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+ printers.add(printer);
+ addPrinters(printers);
+ }
+ }
+
+ @Override
+ public void onValidatePrinters(List<PrinterId> printerIds) {
+ Log.i(LOG_TAG, "FakePrinterDiscoverySession#onValidatePrinters()");
+ }
+
+ @Override
+ public void onStopPrinterStateTracking(PrinterId printerId) {
+ Log.i(LOG_TAG, "FakePrinterDiscoverySession#onStopPrinterStateTracking()");
+ }
+
+ private void addFirstBatchFakePrinters() {
+ List<PrinterInfo> printers = mFakePrinters.subList(0, mFakePrinters.size() / 2);
+ addPrinters(printers);
+ }
+
+ private void addSecondBatchFakePrinters() {
+ List<PrinterInfo> printers = mFakePrinters.subList(0, mFakePrinters.size() / 2
+ /* mFakePrinters.size() / 2, mFakePrinters.size()*/);
+ final int printerCount = mFakePrinters.size();
+ for (int i = printerCount - 1; i >= 0; i--) {
+ PrinterInfo printer = new PrinterInfo.Builder(mFakePrinters.get(i))
+ .setStatus(PrinterInfo.STATUS_UNAVAILABLE).build();
+ printers.add(printer);
+ }
+ addPrinters(printers);
+ }
+
+ private PrinterInfo findPrinterInfo(PrinterId printerId) {
+ List<PrinterInfo> printers = getPrinters();
+ final int printerCount = getPrinters().size();
+ for (int i = 0; i < printerCount; i++) {
+ PrinterInfo printer = printers.get(i);
+ if (printer.getId().equals(printerId)) {
+ return printer;
+ }
+ }
+ return null;
+ }
+
+ private void cancellAddingFakePrinters() {
+ mSesionHandler.removeMessages(SessionHandler.MSG_ADD_FIRST_BATCH_FAKE_PRINTERS);
+ mSesionHandler.removeMessages(SessionHandler.MSG_ADD_SECOND_BATCH_FAKE_PRINTERS);
+ }
+
+ final class SessionHandler extends Handler {
+ public static final int MSG_ADD_FIRST_BATCH_FAKE_PRINTERS = 1;
+ public static final int MSG_ADD_SECOND_BATCH_FAKE_PRINTERS = 2;
+
+ public SessionHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_ADD_FIRST_BATCH_FAKE_PRINTERS: {
+ addFirstBatchFakePrinters();
+ } break;
+
+ case MSG_ADD_SECOND_BATCH_FAKE_PRINTERS: {
+ addSecondBatchFakePrinters();
+ } break;
+ }
+ }
+ }
+ }
}
diff --git a/PrintService2/src/foo/bar/printservice2/MyPrintService.java b/PrintService2/src/foo/bar/printservice2/MyPrintService.java
index 8a4227a..ffc91fa 100644
--- a/PrintService2/src/foo/bar/printservice2/MyPrintService.java
+++ b/PrintService2/src/foo/bar/printservice2/MyPrintService.java
@@ -121,7 +121,7 @@ public class MyPrintService extends PrintService {
PrintJobInfo info = printJob.getInfo();
Toast.makeText(MyPrintService.this,
- "Printer: " + info.getPrinterId().getLocalId()
+ "Printer: " + info.getPrinterId().getName()
+ " copies: " + info.getAttributes().getCopies(),
Toast.LENGTH_SHORT).show();
@@ -166,7 +166,7 @@ public class MyPrintService extends PrintService {
.create();
List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
printers.add(printer);
- addDiscoveredPrinters(printers);
+ addPrinters(printers);
}
private void addSecondFakePrinter() {
@@ -204,7 +204,7 @@ public class MyPrintService extends PrintService {
.create();
List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
printers.add(printer);
- addDiscoveredPrinters(printers);
+ addPrinters(printers);
}
private final class MyHandler extends Handler {
diff --git a/procstatlog/procstatlog.c b/procstatlog/procstatlog.c
index c4c39e3..44bf7bd 100644
--- a/procstatlog/procstatlog.c
+++ b/procstatlog/procstatlog.c
@@ -274,7 +274,7 @@ static struct data *read_stats(char *names[], int name_count) {
}
if (pid_count >= sizeof(pids) / sizeof(pids[0])) {
- fprintf(stderr, "warning: >%d processes\n", pid_count);
+ fprintf(stderr, "warning: >%zu processes\n", pid_count);
} else {
pids[pid_count++] = pid;
}