/*
* Copyright (C) 2007 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.tencent.tws.assistant.services;
import android.app.Service;
import android.util.Log;
import android.util.Slog;
import android.app.Notification;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.Intent;
import android.os.Process;
import android.os.Binder;
import android.os.UserHandle;
import android.os.IBinder;
import android.app.INotificationManager;
import android.content.Context;
import android.os.ServiceManager;
import android.os.RemoteException;
import com.tencent.tws.assistant.services.ITwsNotificationManager;
import com.tencent.tws.assistant.provider.TwsSettings;
import android.content.ContentResolver;
import java.util.ArrayList;
import java.util.List;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import com.android.internal.os.AtomicFile;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import com.android.internal.util.FastXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import libcore.io.IoUtils;
import android.util.Xml;
import java.io.File;
import java.util.HashSet;
import java.util.Iterator;
/** {@hide} */
public class TwsNotificationManagerService extends ITwsNotificationManager.Stub {
private static final String TAG = "TwsNotificationManagerService";
private static final boolean DBG = false;
private static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
private static final int DB_VERSION = 1;
private static final String TAG_BODY = "notification-policy";
private static final String ATTR_VERSION = "version";
private static final String TAG_BLOCKED_PKGS = "blocked-packages";
private static final String TAG_PACKAGE = "package";
private static final String ATTR_NAME ="name";
private static final String ATTR_NTF ="notificaiton";
private static final String ATTR_STATUS ="status";
private static final String ATTR_ICON ="icon";
public static final String EXTRA_CMD_TPYE = "cmd_type";
public static final String EXTRA_PKG_NAME = "pkg_name";
public static final String EXTRA_PKG_INSTALL = "bInstall";
public static final int CMD_PKG_INSTALL_NOTIFY = 1;
private HashSet<String> mBlockedPackages = new HashSet<String>();
private AtomicFile mPolicyFile;
private INotificationManager mNotificationMgr = null;
private Context mContext = null;
private ContentResolver resolver = null;
class NotificationItemInfo {
private String mPkg;
private boolean bShowNotification;
private boolean bShowStatus;
private boolean bShowIcon;
}
class AppItemInfo {
private String mPkg;
}
List<NotificationItemInfo> mThirdBlockedItems = new ArrayList<NotificationItemInfo>();
List<AppItemInfo> mAppList = new ArrayList<AppItemInfo>();
private List<String> mWhiteList = new ArrayList<String>();
private List<String> mBlackList = new ArrayList<String>();
private final static int UPDATE_FLAG_NTY = 0;
private final static int UPDATE_FLAG_STATUS = 1;
private final static int UPDATE_FLAG_ICON = 2;
TwsNotificationManagerService(Context context) {
mContext = context;
mNotificationMgr = INotificationManager.Stub.asInterface(ServiceManager.getService(Context.NOTIFICATION_SERVICE));
loadBlockDb();
resolver = mContext.getContentResolver();
// Log.d(TAG, "tws notification manager service started");
}
public void setNotificationsEnabledForPackage(String pkg, boolean enabled, int flag , int id) {
ensureCallerSystem();
if (DBG) {
Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
}
updateThirdBlockedItems(pkg,enabled,flag);
if (enabled && flag == UPDATE_FLAG_NTY) {
if("com.android.providers.downloads.ui".equals(pkg)||"com.android.providers.downloads".equals(pkg)){
mBlockedPackages.remove("com.android.providers.downloads");
mBlockedPackages.remove("com.android.providers.downloads.ui");
}else{
mBlockedPackages.remove(pkg);
}
} else if(!enabled && (flag == UPDATE_FLAG_NTY || flag == UPDATE_FLAG_STATUS)){
if("com.android.providers.downloads.ui".equals(pkg)||"com.android.providers.downloads".equals(pkg)){
mBlockedPackages.add("com.android.providers.downloads");
mBlockedPackages.add("com.android.providers.downloads.ui");
pkg = "com.android.providers.downloads";
}else{
mBlockedPackages.add(pkg);
}
// Now, cancel any outstanding notifications that are part of a just-disabled app
if (ENABLE_BLOCKED_NOTIFICATIONS) {
int INVALID_VAL = -1;
if(id != INVALID_VAL) {
try {
mNotificationMgr.cancelAllNotifications(pkg,UserHandle.USER_ALL);
} catch (RemoteException e)
{
e.printStackTrace();
}
}
}
// Don't bother canceling toasts, they'll go away soon enough.
}
if(DBG){
for (NotificationItemInfo mItem : mThirdBlockedItems) {
// Log.d(TAG,"mItem pkg="+mItem.mPkg+";bShowNotification = "+mItem.bShowNotification+";bShowStatus = "+mItem.bShowStatus+";bShowIcon = "+mItem.bShowIcon);
}
}
writeBlockDb();
// Log.d(TAG,"writeBlockDb mThirdBlockedItems size = "+mThirdBlockedItems.size());
}
public boolean shouldShowNotification(Notification notification, String pkg) {
/*if(isNotificationOnGoing(notification)){
if(!isStatusEnabledForPackage(pkg)){
Log.d(TAG, "shouldShowNotification 0 :" + pkg);
return false;
}
} else */if(!isNotificationsEnabledForPackage(pkg)){
// Log.d(TAG,"shouldShowNotification 1 :"+pkg);
return false;
}
return true;
}
public boolean isStatusEnabledForPackage(String pkg) {
if(!ENABLE_BLOCKED_NOTIFICATIONS) return true;
return isStatusEnabledForPackageInternal(pkg);
}
public boolean isNotificationsEnabledForPackage(String pkg) {
if(!ENABLE_BLOCKED_NOTIFICATIONS) return true;
if(DBG) Log.d(TAG,"mThirdBlockedItems size = "+mThirdBlockedItems.size());
return isNotificationsEnabledForPackageInternal(pkg);
}
public boolean shouldShowIcon(String pkg) {
boolean enabled = true;
// Log.d(TAG,"shouldShowIcon :"+pkg);
for(NotificationItemInfo mBlockedItem : mThirdBlockedItems){
if(pkg.equalsIgnoreCase(mBlockedItem.mPkg) && mBlockedItem.bShowIcon){
enabled = false;
// Log.d(TAG,"shouldShowIcon :enabled = true");
break;
}
}
return enabled;
}
public void installDeletePackage(String pkg , boolean bInstall) {
if(DBG) Log.d(TAG, "installDeletePackage(" + pkg + ", " + bInstall + ")");
if(pkg == null) {
return;
}
synchronized(mThirdBlockedItems) {
final int currentIconValue = TwsSettings.System.getInt(resolver, "notification_display_icon_label",2);
if(bInstall){
NotificationItemInfo mTwsNotificationItemInfo = new NotificationItemInfo();
mTwsNotificationItemInfo.mPkg = pkg;
if(currentIconValue == 0){
mTwsNotificationItemInfo.bShowIcon = true;
mTwsNotificationItemInfo.bShowNotification = true;
}
else if(currentIconValue == 1){
mTwsNotificationItemInfo.bShowIcon = false;
mTwsNotificationItemInfo.bShowNotification = true;
}
else{
if(isPhoneOrTws(pkg)){
mTwsNotificationItemInfo.bShowIcon = true;
}
else{
mTwsNotificationItemInfo.bShowIcon = false;
}
mTwsNotificationItemInfo.bShowNotification = true;
}
mTwsNotificationItemInfo.bShowStatus = false;
mThirdBlockedItems.add(mTwsNotificationItemInfo);
}else{
NotificationItemInfo mItem = findItem(pkg);
if(mItem == null)
return;
mThirdBlockedItems.remove(mItem);
}
writeBlockDb();
}
}
void loadBlockDb() {
synchronized(mThirdBlockedItems) {
getAppList();
Slog.v(TAG, "mAppList size = " + mAppList.size());
boolean bWrite = false;
if (mPolicyFile == null) {
File mFile = new File("/data/system/notification_policy.xml");
try{
if(!mFile.exists()){
boolean b = mFile.createNewFile();
}
}catch(Exception e){
e.printStackTrace();
}
File dir = new File("/data/system");
//notification_comfig.xml
mPolicyFile = new AtomicFile(mFile);
mBlockedPackages.clear();
mThirdBlockedItems.clear();
// Log.d(TAG,"twsThirdBlockedItems0 size = "+mThirdBlockedItems.size());
FileInputStream infile = null;
try {
infile = mPolicyFile.openRead();
final XmlPullParser parser = Xml.newPullParser();
parser.setInput(infile, null);
int type;
String tag;
int version = DB_VERSION;
while ((type = parser.next()) != END_DOCUMENT) {
tag = parser.getName();
if (type == START_TAG) {
if (TAG_BODY.equals(tag)) {
version = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));
} else if (TAG_BLOCKED_PKGS.equals(tag)) {
while ((type = parser.next()) != END_DOCUMENT) {
tag = parser.getName();
if (TAG_PACKAGE.equals(tag) && type == END_TAG) {
//mBlockedPackages.add(parser.getAttributeValue(null, ATTR_NAME));
NotificationItemInfo mItemInfo = new NotificationItemInfo();
mItemInfo.mPkg = parser.getAttributeValue(null, ATTR_NAME);
mItemInfo.bShowNotification = stringToBool(parser.getAttributeValue(null, ATTR_NTF));
mItemInfo.bShowStatus = stringToBool("0");
mItemInfo.bShowIcon = stringToBool(parser.getAttributeValue(null, ATTR_ICON));
mThirdBlockedItems.add(mItemInfo);
} else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
break;
}
}
}
}
}
// Log.d(TAG,"twsThirdBlockedItems1 size = "+mThirdBlockedItems.size());
if(DBG){
for (NotificationItemInfo mItem : mThirdBlockedItems) {
// Log.d(TAG,"mItem pkg="+mItem.mPkg+";bShowNotification = "+mItem.bShowNotification+";bShowStatus = "+mItem.bShowStatus+";bShowIcon = "+mItem.bShowIcon);
}
}
List<NotificationItemInfo> tmpAppList = new ArrayList<NotificationItemInfo>();
for(AppItemInfo mItem : mAppList){
if(DBG)Log.d(TAG, "mItem.mPkg = " + mItem.mPkg);
if(mThirdBlockedItems.size() <= 0){
NotificationItemInfo mNewItemInfo = new NotificationItemInfo();
mNewItemInfo.mPkg = mItem.mPkg;
mNewItemInfo.bShowNotification = true;
mNewItemInfo.bShowStatus = false;//
if(isPhoneOrTws(mItem.mPkg))
mNewItemInfo.bShowIcon = true;
else
mNewItemInfo.bShowIcon = false;
tmpAppList.add(mNewItemInfo);
mBlockedPackages.add(mNewItemInfo.mPkg);
bWrite = true;
if(DBG)Log.d(TAG, "twsBlockedPackages0.add");
}else{
if(!containItem(mItem,mThirdBlockedItems)){
NotificationItemInfo mNewItemInfo = new NotificationItemInfo();
mNewItemInfo.mPkg = mItem.mPkg;
mNewItemInfo.bShowNotification = true;
mNewItemInfo.bShowStatus = false;//
mNewItemInfo.bShowIcon = true;
tmpAppList.add(mNewItemInfo);
mBlockedPackages.add(mNewItemInfo.mPkg);
bWrite = true;
if(DBG)Log.d(TAG, "twsBlockedPackages1.add");
}
}
}
// Log.d(TAG, "tmpAppList size = " + tmpAppList.size());
mThirdBlockedItems.addAll(tmpAppList);
if(bWrite){
if(DBG){
for (NotificationItemInfo mItem : mThirdBlockedItems) {
Log.d(TAG,"mItem pkg="+mItem.mPkg+";bShowNotification = "+mItem.bShowNotification+";bShowStatus = "+mItem.bShowStatus+";bShowIcon = "+mItem.bShowIcon);
}
}
writeBlockDb();
bWrite = false;
}
} catch (FileNotFoundException e) {
// No data yet
} catch (IOException e) {
Log.wtf(TAG, "Unable to read blocked notifications database", e);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Unable to parse blocked notifications database", e);
} catch (XmlPullParserException e) {
Log.wtf(TAG, "Unable to parse blocked notifications database", e);
} finally {
IoUtils.closeQuietly(infile);
}
}
}
}
void writeBlockDb() {
synchronized(mThirdBlockedItems) {
FileOutputStream outfile = null;
try {
outfile = mPolicyFile.startWrite();
XmlSerializer out = new FastXmlSerializer();
out.setOutput(outfile, "utf-8");
out.startDocument(null, true);
out.startTag(null, TAG_BODY); {
out.attribute(null, ATTR_VERSION, String.valueOf(DB_VERSION));
out.startTag(null, TAG_BLOCKED_PKGS); {
// write all known network policies
for (NotificationItemInfo mItem : mThirdBlockedItems) {
out.startTag(null, TAG_PACKAGE); {
out.attribute(null, ATTR_NAME, mItem.mPkg);
out.attribute(null, ATTR_NTF, boolToString(mItem.bShowNotification));
out.attribute(null, ATTR_STATUS, boolToString(mItem.bShowStatus));
out.attribute(null, ATTR_ICON, boolToString(mItem.bShowIcon));
} out.endTag(null, TAG_PACKAGE);
}
} out.endTag(null, TAG_BLOCKED_PKGS);
} out.endTag(null, TAG_BODY);
out.endDocument();
mPolicyFile.finishWrite(outfile);
} catch (IOException e) {
if (outfile != null) {
mPolicyFile.failWrite(outfile);
}
}
}
}
// Unchecked. Not exposed via Binder, but can be called in the course of enqueue*().
boolean isNotificationsEnabledForPackageInternal(String pkg) {
boolean enabled = true;
for(NotificationItemInfo mBlockedItem : mThirdBlockedItems){
if(pkg.equalsIgnoreCase(mBlockedItem.mPkg) && (!mBlockedItem.bShowNotification)){
enabled = false;
break;
}
}
if (DBG) {
Slog.v(TAG, "notifications are " + (enabled?"en":"dis") + "abled for " + pkg);
Iterator iterator = mBlockedPackages.iterator();
while(iterator.hasNext())
Slog.w(TAG,"isNotificationsEnabledForPackageInternal pkg="+iterator.next());
}
return enabled;
}
// Unchecked. Not exposed via Binder, but can be called in the course of enqueue*().
boolean isStatusEnabledForPackageInternal(String pkg) {
boolean enabled = true;
for(NotificationItemInfo mBlockedItem : mThirdBlockedItems){
if(pkg.equalsIgnoreCase(mBlockedItem.mPkg) && (!mBlockedItem.bShowStatus)){
enabled = false;
break;
}
}
return enabled;
}
private void updateThirdBlockedItems(String pkg, boolean mEnabled,int flags){
synchronized(mThirdBlockedItems) {
// Log.d(TAG,"mThirdBlockedItems size = "+mThirdBlockedItems.size());
for (NotificationItemInfo mItem : mThirdBlockedItems) {
if(mItem.mPkg.equalsIgnoreCase(pkg)){
boolean bShowNty = mItem.bShowNotification;
boolean bShowStatus = mItem.bShowStatus;
boolean bShowIcon = mItem.bShowIcon;
NotificationItemInfo mTwsNotificationItemInfo = new NotificationItemInfo();
mTwsNotificationItemInfo.mPkg = pkg;
if(flags == UPDATE_FLAG_NTY){
mTwsNotificationItemInfo.bShowStatus = bShowStatus;
mTwsNotificationItemInfo.bShowIcon = bShowIcon;
mTwsNotificationItemInfo.bShowNotification = mEnabled;
}else if(flags == UPDATE_FLAG_STATUS){
mTwsNotificationItemInfo.bShowStatus = mEnabled;
mTwsNotificationItemInfo.bShowIcon = bShowIcon;
mTwsNotificationItemInfo.bShowNotification = bShowNty;
}else if(flags == UPDATE_FLAG_ICON){
mTwsNotificationItemInfo.bShowStatus = bShowStatus;
mTwsNotificationItemInfo.bShowIcon = mEnabled;
mTwsNotificationItemInfo.bShowNotification = bShowNty;
}
// Log.d(TAG,"updateThirdBlockedItems pkg = "+pkg);
mThirdBlockedItems.remove(mItem);
mThirdBlockedItems.add(mTwsNotificationItemInfo);
break;
}
}
}
}
void ensureCallerSystem() {
int uid = Binder.getCallingUid();
if (uid == Process.SYSTEM_UID || uid == 0) {
return;
}
throw new SecurityException("Disallowed call for uid " + uid);
}
private void getAppList() {
mAppList.clear();
//List<ResolveInfo> appPackageInfos = getAllTheLaunch();
PackageManager mPm = mContext.getPackageManager();
List<PackageInfo> packs = mPm.getInstalledPackages(0);
for (int i = 0; i < packs.size(); i++) {
PackageInfo packInf = packs.get(i);
ApplicationInfo pinfo = null;
try {
pinfo = mPm.getApplicationInfo(packInf.packageName,
PackageManager.GET_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
if(pinfo == null){
continue;
}
if (!isSystemPackage(pinfo.packageName)&& !isInBlackList(pinfo.packageName)) {
AppItemInfo shareInfo = new AppItemInfo();
shareInfo.mPkg = pinfo.packageName;
mAppList.add(shareInfo);
}
}
}
private List<ResolveInfo> getAllTheLaunch() {
Intent it = new Intent(Intent.ACTION_MAIN);
// it.addCategory(Intent.CATEGORY_HOME);
it.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> ra = mContext.getPackageManager().queryIntentActivities(it, 0);
return ra;
}
private void initBlackList(){
mBlackList.clear();
mBlackList.add("com.android.OriginalSettings");
mBlackList.add("com.android.systemui");
mBlackList.add("com.tencent.nanji.updater");
}
private void initWhiteList(){
mWhiteList.clear();
//mWhiteList.add("com.android.contacts");
//mWhiteList.add("com.tencent.launcher");
//mWhiteList.add("com.android.deskclock");
//mWhiteList.add("com.android.calendar");
mWhiteList.add("com.immomo.momo");
mWhiteList.add("com.sina.weibo");
mWhiteList.add("com.tencent.mobileqq");
mWhiteList.add("com.tencent.mm");
mWhiteList.add("com.tencent.WBlog");
mWhiteList.add("com.android.OriginalSettings");
//mWhiteList.add("com.android.providers.downloads.ui");
//mWhiteList.add("com.android.calculator2");
//mWhiteList.add("com.tencent.qqmusic");
//mWhiteList.add("com.android.providers.downloads");
}
private boolean isPhoneOrTws(String packageName) {
initWhiteList();
for(String item : mWhiteList){
if(item.equalsIgnoreCase(packageName)){
return true;
}
}
return false;
}
private boolean isInBlackList(String packageName) {
initBlackList();
for(String item : mBlackList){
if(item.equalsIgnoreCase(packageName)){
return true;
}
}
return false;
}
private boolean isSystemPackage(String packagename) {
try {
PackageInfo pInfo = mContext.getPackageManager().getPackageInfo(packagename, 0);
if (isSystemApp(pInfo) || isSystemUpdateApp(pInfo)) {
return true;
} else {
return false;
}
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
private boolean isSystemApp(PackageInfo info) {
return ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
}
private boolean isSystemUpdateApp(PackageInfo info) {
return ((info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
}
private String boolToString(boolean b){
int mStr = b ? 1 : 0;
return String.valueOf(mStr);
}
private boolean stringToBool(String str){
if(str == null)
return true;
if(str.equalsIgnoreCase("1"))
return true;
else
return false;
}
private boolean containItem(AppItemInfo mBlockedItem,List<NotificationItemInfo> mThirdBlockedItems){
for(NotificationItemInfo mItem : mThirdBlockedItems){
if(mItem.mPkg.equalsIgnoreCase(mBlockedItem.mPkg))
return true;
}
return false;
}
private NotificationItemInfo findItem(String pkg){
for(NotificationItemInfo mItem : mThirdBlockedItems){
if(pkg.equalsIgnoreCase(mItem.mPkg))
return mItem;
}
return null;
}
private boolean isNotificationOnGoing(Notification notification) {
boolean mFlags = (notification.flags & (Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR)) == 0 ? false
: true;
return mFlags;
}
}