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