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                 mCurrentMode = MODE.CANCEL_MODE;
84             } else if (ACTION_CHANGE_EXECUTION_MODE.equals(action)) {
85                 mCurrentMode = MODE.EXECUTION_MODE;
86             }
87         }
88     };
89 
90     @Override
onCreate()91     public void onCreate() {
92         mTargetPackageName = getTargetPackageName();
93         mFrameLayout = new FrameLayout(getBaseContext());
94         IntentFilter filter = new IntentFilter();
95         filter.addAction(ACTION_CHANGE_CANCEL_MODE);
96         filter.addAction(ACTION_CHANGE_EXECUTION_MODE);
97         registerReceiver(mReceiver, filter);
98     }
99 
100     @Override
onStartCommand(Intent intent, int flags, int startId)101     public int onStartCommand(Intent intent, int flags, int startId) {
102         return START_STICKY;
103     }
104 
105     @Override
onDestroy()106     public void onDestroy() {
107         unregisterReceiver(mReceiver);
108         mProgressBar = null;
109         mFrameLayout.removeAllViews();
110         mFrameLayout = null;
111     }
112 
113     @Override
onNotificationPosted(StatusBarNotification sbn)114     public void onNotificationPosted(StatusBarNotification sbn) {
115         String pkgName = sbn.getPackageName();
116         if (mTargetPackageName.equals(pkgName)) {
117             if (MODE.CANCEL_MODE.equals(mCurrentMode)) {
118                 try {
119                     mCancelled = doCancel(sbn.getNotification());
120                 } catch (Exception e) {
121                     Log.d(TAG, "Error occurs when cancel notification.", e);
122                 }
123             }
124         }
125     }
126 
127     @Override
onNotificationRemoved(StatusBarNotification sbn)128     public void onNotificationRemoved(StatusBarNotification sbn) {
129         String pkgName = sbn.getPackageName();
130         if (!mTargetPackageName.equals(pkgName)) {
131             return;
132         }
133 
134         Intent intent = new Intent(ACTION_OPERATION_RESULT);
135         if (MODE.CANCEL_MODE.equals(mCurrentMode)) {
136             intent.putExtra(EXTRA_RESULT, mCancelled);
137             if (!mCancelled) {
138                 intent.putExtra(EXTRA_ERROR_REASON, "Cannot executed cancel");
139             }
140         } else if (MODE.EXECUTION_MODE.equals(mCurrentMode)) {
141             boolean isStartProgress = isStartProgress(sbn.getNotification());
142             intent.putExtra(EXTRA_RESULT, isStartProgress);
143             if (!isStartProgress) {
144                 intent.putExtra(EXTRA_ERROR_REASON, "Progress does not displayed correctly.");
145             }
146         }
147         sendBroadcast(intent);
148     }
149 
doCancel(Notification noti)150     private boolean doCancel(Notification noti)
151             throws NameNotFoundException, PendingIntent.CanceledException {
152         if (!isStartProgress(noti)) {
153             return false;
154         }
155 
156         Notification.Action aList [] = noti.actions;
157         if (aList == null) {
158             return false;
159         }
160 
161         boolean result = false;
162         for (Notification.Action item : aList) {
163             Context android_context = getBaseContext().createPackageContext(ANDROID_PACKAGENAME,
164                     Context.CONTEXT_RESTRICTED);
165             int res_id = android_context.getResources().getIdentifier(CANCEL_RES_NAME,
166                     "string", ANDROID_PACKAGENAME);
167             final String cancel_label = android_context.getResources().getString(res_id);
168 
169             if (cancel_label.equals(item.title)) {
170                 item.actionIntent.send();
171                 result = true;
172             }
173         }
174         return result;
175     }
176 
isStartProgress(Notification notifiction)177     private boolean isStartProgress(Notification notifiction) {
178         ProgressBar progressBar = getProgresssBar(getRemoteViews(notifiction));
179         return (progressBar != null) ? progressBar.getProgress() > 0 : false;
180     }
181 
getRemoteViews(Notification notifiction)182     private RemoteViews getRemoteViews(Notification notifiction) {
183         Notification.Builder builder = Notification.Builder.recoverBuilder(
184             getBaseContext(), notifiction);
185         if (builder == null) {
186             return null;
187         }
188 
189         return builder.createContentView();
190     }
191 
getProgresssBar(RemoteViews remoteViews)192     private ProgressBar getProgresssBar(RemoteViews remoteViews) {
193         if (remoteViews == null) {
194             return null;
195         }
196 
197         View view = remoteViews.apply(getBaseContext(), mFrameLayout);
198         return getProgressBarImpl(view);
199     }
200 
getProgressBarImpl(View view)201     private ProgressBar getProgressBarImpl(View view) {
202         if (view == null || !(view instanceof ViewGroup)) {
203             return null;
204         }
205 
206         ViewGroup viewGroup = (ViewGroup)view;
207         if (viewGroup.getChildCount() <= 0) {
208             return null;
209         }
210 
211         ProgressBar result = null;
212         for (int i = 0; i < viewGroup.getChildCount(); i++) {
213             View v = viewGroup.getChildAt(i);
214             if (v instanceof ProgressBar) {
215                 result = ((ProgressBar)v);
216                 break;
217             } else if (v instanceof ViewGroup) {
218                 result = getProgressBarImpl(v);
219                 if (result != null) {
220                     break;
221                 }
222             }
223         }
224         return result;
225     }
226 
getTargetPackageName()227     private String getTargetPackageName() {
228         final PackageManager pm = getPackageManager();
229 
230         final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
231         intent.addCategory(Intent.CATEGORY_OPENABLE);
232         intent.setType("*/*");
233         final ResolveInfo ri = pm.resolveActivity(intent, 0);
234         return ri.activityInfo.packageName;
235     }
236 }
237