1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.documentsui.services;
17 
18 import android.app.Notification;
19 import android.app.PendingIntent;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.pm.PackageManager;
25 import android.content.pm.PackageManager.NameNotFoundException;
26 import android.content.pm.ResolveInfo;
27 import android.service.notification.NotificationListenerService;
28 import android.service.notification.StatusBarNotification;
29 import android.util.Log;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.FrameLayout;
33 import android.widget.ProgressBar;
34 import android.widget.RemoteViews;
35 
36 /**
37 * This class receives a callback when Notification is posted or removed
38 * and monitors the Notification status.
39 * And, this sends the operation's result by Broadcast.
40 */
41 public class TestNotificationService extends NotificationListenerService {
42     private static final String TAG = "TestNotificationService";
43 
44     public static final String ACTION_CHANGE_CANCEL_MODE =
45             "com.android.documentsui.services.TestNotificationService.ACTION_CHANGE_CANCEL_MODE";
46 
47     public static final String ACTION_CHANGE_EXECUTION_MODE =
48             "com.android.documentsui.services.TestNotificationService.ACTION_CHANGE_EXECUTION_MODE";
49 
50     public static final String ACTION_OPERATION_RESULT =
51             "com.android.documentsui.services.TestNotificationService.ACTION_OPERATION_RESULT";
52 
53     public static final String ANDROID_PACKAGENAME = "android";
54 
55     public static final String CANCEL_RES_NAME = "cancel";
56 
57     public static final String EXTRA_RESULT =
58             "com.android.documentsui.services.TestNotificationService.EXTRA_RESULT";
59 
60     public static final String EXTRA_ERROR_REASON =
61             "com.android.documentsui.services.TestNotificationService.EXTRA_ERROR_REASON";
62 
63     public enum MODE {
64         CANCEL_MODE,
65         EXECUTION_MODE;
66     }
67 
68     private static String mTargetPackageName;
69 
70     private MODE mCurrentMode = MODE.CANCEL_MODE;
71 
72     private boolean mCancelled = false;
73 
74     private FrameLayout mFrameLayout = null;
75 
76     private ProgressBar mProgressBar = null;
77 
78     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
79         @Override
80         public void onReceive(Context context, Intent intent) {
81             String action = intent.getAction();
82             if (ACTION_CHANGE_CANCEL_MODE.equals(action)) {
83                 Log.i(TAG, "Received cancel mode");
84                 mCurrentMode = MODE.CANCEL_MODE;
85             } else if (ACTION_CHANGE_EXECUTION_MODE.equals(action)) {
86                 Log.i(TAG, "Received execution mode");
87                 mCurrentMode = MODE.EXECUTION_MODE;
88             }
89         }
90     };
91 
92     @Override
onCreate()93     public void onCreate() {
94         mTargetPackageName = getTargetPackageName();
95         mFrameLayout = new FrameLayout(getBaseContext());
96         IntentFilter filter = new IntentFilter();
97         filter.addAction(ACTION_CHANGE_CANCEL_MODE);
98         filter.addAction(ACTION_CHANGE_EXECUTION_MODE);
99         registerReceiver(mReceiver, filter);
100     }
101 
102     @Override
onStartCommand(Intent intent, int flags, int startId)103     public int onStartCommand(Intent intent, int flags, int startId) {
104         return START_STICKY;
105     }
106 
107     @Override
onDestroy()108     public void onDestroy() {
109         unregisterReceiver(mReceiver);
110         mProgressBar = null;
111         mFrameLayout.removeAllViews();
112         mFrameLayout = null;
113     }
114 
115     @Override
onNotificationPosted(StatusBarNotification sbn)116     public void onNotificationPosted(StatusBarNotification sbn) {
117         String pkgName = sbn.getPackageName();
118         Log.i(TAG, "Entering notification posted cancelMode = " + MODE.CANCEL_MODE
119                 .equals(mCurrentMode) + " targetPackageName " + mTargetPackageName + " packageName "
120                 + pkgName);
121         if (mTargetPackageName.equals(pkgName)) {
122             if (MODE.CANCEL_MODE.equals(mCurrentMode)) {
123                 try {
124                     mCancelled = doCancel(sbn.getNotification());
125                 } catch (Exception e) {
126                     Log.d(TAG, "Error occurs when cancel notification.", e);
127                 }
128             }
129         }
130     }
131 
132     @Override
onNotificationRemoved(StatusBarNotification sbn)133     public void onNotificationRemoved(StatusBarNotification sbn) {
134         Log.i(TAG, "Entering notification removed cancelMode = " + MODE.CANCEL_MODE
135                 .equals(mCurrentMode));
136         String pkgName = sbn.getPackageName();
137         if (!mTargetPackageName.equals(pkgName)) {
138             return;
139         }
140 
141         Intent intent = new Intent(ACTION_OPERATION_RESULT);
142         if (MODE.CANCEL_MODE.equals(mCurrentMode)) {
143             intent.putExtra(EXTRA_RESULT, mCancelled);
144             if (!mCancelled) {
145                 intent.putExtra(EXTRA_ERROR_REASON, "Cannot executed cancel");
146             }
147         } else if (MODE.EXECUTION_MODE.equals(mCurrentMode)) {
148             boolean isStartProgress = isStartProgress(sbn.getNotification());
149             intent.putExtra(EXTRA_RESULT, isStartProgress);
150             if (!isStartProgress) {
151                 intent.putExtra(EXTRA_ERROR_REASON, "Progress does not displayed correctly.");
152             }
153         }
154         sendBroadcast(intent);
155     }
156 
doCancel(Notification noti)157     private boolean doCancel(Notification noti)
158             throws NameNotFoundException, PendingIntent.CanceledException {
159         if (!isStartProgress(noti)) {
160             return false;
161         }
162 
163         Notification.Action aList [] = noti.actions;
164         if (aList == null) {
165             return false;
166         }
167 
168         boolean result = false;
169         for (Notification.Action item : aList) {
170             Context android_context = getBaseContext().createPackageContext(ANDROID_PACKAGENAME,
171                     Context.CONTEXT_RESTRICTED);
172             int res_id = android_context.getResources().getIdentifier(CANCEL_RES_NAME,
173                     "string", ANDROID_PACKAGENAME);
174             final String cancel_label = android_context.getResources().getString(res_id);
175 
176             if (cancel_label.equals(item.title)) {
177                 item.actionIntent.send();
178                 result = true;
179             }
180         }
181         return result;
182     }
183 
isStartProgress(Notification notifiction)184     private boolean isStartProgress(Notification notifiction) {
185         ProgressBar progressBar = getProgresssBar(getRemoteViews(notifiction));
186         return (progressBar != null) ? progressBar.getProgress() > 0 : false;
187     }
188 
getRemoteViews(Notification notifiction)189     private RemoteViews getRemoteViews(Notification notifiction) {
190         Notification.Builder builder = Notification.Builder.recoverBuilder(
191             getBaseContext(), notifiction);
192         if (builder == null) {
193             return null;
194         }
195 
196         return builder.createContentView();
197     }
198 
getProgresssBar(RemoteViews remoteViews)199     private ProgressBar getProgresssBar(RemoteViews remoteViews) {
200         if (remoteViews == null) {
201             return null;
202         }
203 
204         View view = remoteViews.apply(getBaseContext(), mFrameLayout);
205         return getProgressBarImpl(view);
206     }
207 
getProgressBarImpl(View view)208     private ProgressBar getProgressBarImpl(View view) {
209         if (view == null || !(view instanceof ViewGroup)) {
210             return null;
211         }
212 
213         ViewGroup viewGroup = (ViewGroup)view;
214         if (viewGroup.getChildCount() <= 0) {
215             return null;
216         }
217 
218         ProgressBar result = null;
219         for (int i = 0; i < viewGroup.getChildCount(); i++) {
220             View v = viewGroup.getChildAt(i);
221             if (v instanceof ProgressBar) {
222                 result = ((ProgressBar)v);
223                 break;
224             } else if (v instanceof ViewGroup) {
225                 result = getProgressBarImpl(v);
226                 if (result != null) {
227                     break;
228                 }
229             }
230         }
231         return result;
232     }
233 
getTargetPackageName()234     private String getTargetPackageName() {
235         final PackageManager pm = getPackageManager();
236 
237         final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
238         intent.addCategory(Intent.CATEGORY_OPENABLE);
239         intent.setType("*/*");
240         final ResolveInfo ri = pm.resolveActivity(intent, 0);
241         return ri.activityInfo.packageName;
242     }
243 }
244