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