package com.mixpanel.android.mpmetrics;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.os.Build;
import com.mixpanel.android.takeoverinapp.TakeoverInAppActivity;
import com.mixpanel.android.util.MPLog;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/*
* Copyright 2012 Google Inc.
*
* 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.
*
* This file has been modified from its original version by Mixpanel, Inc. The original
* contents were part of GCMRegistrar, retrieved from
* https://code.google.com/p/gcm/source/browse/gcm-client/src/com/google/android/gcm/GCMRegistrar.java
* on Jan 3, 2013
*/
/* package */ class ConfigurationChecker {
public static String LOGTAG = "MixpanelAPI.ConfigurationChecker";
private static Boolean mTakeoverActivityAvailable;
public static boolean checkBasicConfiguration(Context context) {
final PackageManager packageManager = context.getPackageManager();
final String packageName = context.getPackageName();
if (packageManager == null || packageName == null) {
MPLog.w(LOGTAG, "Can't check configuration when using a Context with null packageManager or packageName");
return false;
}
if (PackageManager.PERMISSION_GRANTED != packageManager.checkPermission("android.permission.INTERNET", packageName)) {
MPLog.w(LOGTAG, "Package does not have permission android.permission.INTERNET - Mixpanel will not work at all!");
MPLog.i(LOGTAG, "You can fix this by adding the following to your AndroidManifest.xml file:\n" +
"<uses-permission android:name=\"android.permission.INTERNET\" />");
return false;
}
return true;
}
public static boolean checkPushConfiguration(Context context) {
if (Build.VERSION.SDK_INT < 8) {
// Not a warning, may be expected behavior
MPLog.i(LOGTAG, "Mixpanel push notifications not supported in SDK " + Build.VERSION.SDK_INT);
return false;
}
final PackageManager packageManager = context.getPackageManager();
final String packageName = context.getPackageName();
if (packageManager == null || packageName == null) {
MPLog.w(LOGTAG, "Can't check configuration when using a Context with null packageManager or packageName");
return false;
}
final String permissionName = packageName + ".permission.C2D_MESSAGE";
// check special permission
try {
packageManager.getPermissionInfo(permissionName, PackageManager.GET_META_DATA);
} catch (final NameNotFoundException e) {
MPLog.w(LOGTAG, "Application does not define permission " + permissionName);
MPLog.i(LOGTAG, "You will need to add the following lines to your application manifest:\n" +
"<permission android:name=\"" + packageName + ".permission.C2D_MESSAGE\" android:protectionLevel=\"signature\" />\n" +
"<uses-permission android:name=\"" + packageName + ".permission.C2D_MESSAGE\" />");
return false;
}
// check regular permissions
if (PackageManager.PERMISSION_GRANTED != packageManager.checkPermission("com.google.android.c2dm.permission.RECEIVE", packageName)) {
MPLog.w(LOGTAG, "Package does not have permission com.google.android.c2dm.permission.RECEIVE");
MPLog.i(LOGTAG, "You can fix this by adding the following to your AndroidManifest.xml file:\n" +
"<uses-permission android:name=\"com.google.android.c2dm.permission.RECEIVE\" />");
return false;
}
if (PackageManager.PERMISSION_GRANTED != packageManager.checkPermission("android.permission.INTERNET", packageName)) {
MPLog.w(LOGTAG, "Package does not have permission android.permission.INTERNET");
MPLog.i(LOGTAG, "You can fix this by adding the following to your AndroidManifest.xml file:\n" +
"<uses-permission android:name=\"android.permission.INTERNET\" />");
return false;
}
if (PackageManager.PERMISSION_GRANTED != packageManager.checkPermission("android.permission.WAKE_LOCK", packageName)) {
MPLog.w(LOGTAG, "Package does not have permission android.permission.WAKE_LOCK");
MPLog.i(LOGTAG, "You can fix this by adding the following to your AndroidManifest.xml file:\n" +
"<uses-permission android:name=\"android.permission.WAKE_LOCK\" />");
return false;
}
// This permission is only required on older devices
if (PackageManager.PERMISSION_GRANTED != packageManager.checkPermission("android.permission.GET_ACCOUNTS", packageName)) {
MPLog.i(LOGTAG, "Package does not have permission android.permission.GET_ACCOUNTS");
MPLog.i(LOGTAG, "Android versions below 4.1 require GET_ACCOUNTS to receive Mixpanel push notifications.\n" +
"Devices with later OS versions will still be able to receive messages, but if you'd like to support " +
"older devices, you'll need to add the following to your AndroidManifest.xml file:\n" +
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\" />");
if (Build.VERSION.SDK_INT < 16) {
return false;
}
}
// check receivers
final PackageInfo receiversInfo;
try {
receiversInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_RECEIVERS);
} catch (final NameNotFoundException e) {
MPLog.w(LOGTAG, "Could not get receivers for package " + packageName);
return false;
}
final ActivityInfo[] receivers = receiversInfo.receivers;
if (receivers == null || receivers.length == 0) {
MPLog.w(LOGTAG, "No receiver for package " + packageName);
MPLog.i(LOGTAG, "You can fix this with the following into your <application> tag:\n" +
samplePushConfigurationMessage(packageName));
return false;
}
final Set<String> allowedReceivers = new HashSet<String>();
for (final ActivityInfo receiver : receivers) {
if ("com.google.android.c2dm.permission.SEND".equals(receiver.permission)) {
allowedReceivers.add(receiver.name);
}
}
if (allowedReceivers.isEmpty()) {
MPLog.w(LOGTAG, "No receiver allowed to receive com.google.android.c2dm.permission.SEND");
MPLog.i(LOGTAG, "You can fix by pasting the following into the <application> tag in your AndroidManifest.xml:\n" +
samplePushConfigurationMessage(packageName));
return false;
}
if (!checkReceiver(context, allowedReceivers, "com.google.android.c2dm.intent.RECEIVE")) {
return false;
}
boolean canRegisterWithPlayServices = false;
try {
Class.forName("com.google.android.gms.common.GooglePlayServicesUtil");
canRegisterWithPlayServices = true;
} catch(final ClassNotFoundException e) {
MPLog.w(LOGTAG, "Google Play Services aren't included in your build- push notifications won't work on Lollipop/API 21 or greater");
MPLog.i(LOGTAG, "You can fix this by adding com.google.android.gms:play-services as a dependency of your gradle or maven project");
}
boolean canRegisterWithRegistrationIntent = true;
if (!checkReceiver(context, allowedReceivers, "com.google.android.c2dm.intent.REGISTRATION")) {
MPLog.i(LOGTAG, "(You can still receive push notifications on Lollipop/API 21 or greater with this configuration)");
canRegisterWithRegistrationIntent = false;
}
return canRegisterWithPlayServices || canRegisterWithRegistrationIntent;
}
public static boolean checkTakeoverInAppActivityAvailable(Context context) {
if (mTakeoverActivityAvailable == null) {
if (Build.VERSION.SDK_INT < MPConfig.UI_FEATURES_MIN_API) {
// No need to log, TakeoverInAppActivity doesn't work on this platform.
mTakeoverActivityAvailable = false;
return mTakeoverActivityAvailable;
}
final Intent takeoverInAppIntent = new Intent(context, TakeoverInAppActivity.class);
takeoverInAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
takeoverInAppIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
final PackageManager packageManager = context.getPackageManager();
final List<ResolveInfo> intentActivities = packageManager.queryIntentActivities(takeoverInAppIntent, 0);
if (intentActivities.size() == 0) {
MPLog.w(LOGTAG, TakeoverInAppActivity.class.getName() + " is not registered as an activity in your application, so takeover in-apps can't be shown.");
MPLog.i(LOGTAG, "Please add the child tag <activity android:name=\"com.mixpanel.android.takeoverinapp.TakeoverInAppActivity\" /> to your <application> tag.");
mTakeoverActivityAvailable = false;
return mTakeoverActivityAvailable;
}
mTakeoverActivityAvailable = true;
}
return mTakeoverActivityAvailable;
}
private static String samplePushConfigurationMessage(String packageName) {
return
"<receiver android:name=\"com.mixpanel.android.mpmetrics.GCMReceiver\"\n" +
" android:permission=\"com.google.android.c2dm.permission.SEND\" >\n" +
" <intent-filter>\n" +
" <action android:name=\"com.google.android.c2dm.intent.RECEIVE\" />\n" +
" <action android:name=\"com.google.android.c2dm.intent.REGISTRATION\" />\n" +
" <category android:name=\"" + packageName + "\" />\n" +
" </intent-filter>\n" +
"</receiver>";
}
private static boolean checkReceiver(Context context, Set<String> allowedReceivers, String action) {
final PackageManager pm = context.getPackageManager();
final String packageName = context.getPackageName();
final Intent intent = new Intent(action);
intent.setPackage(packageName);
final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA);
if (receivers.isEmpty()) {
MPLog.w(LOGTAG, "No receivers for action " + action);
MPLog.i(LOGTAG, "You can fix by pasting the following into the <application> tag in your AndroidManifest.xml:\n" +
samplePushConfigurationMessage(packageName));
return false;
}
// make sure receivers match
for (final ResolveInfo receiver : receivers) {
final String name = receiver.activityInfo.name;
if (!allowedReceivers.contains(name)) {
MPLog.w(LOGTAG, "Receiver " + name + " is not set with permission com.google.android.c2dm.permission.SEND");
MPLog.i(LOGTAG, "Please add the attribute 'android:permission=\"com.google.android.c2dm.permission.SEND\"' to your <receiver> tag");
return false;
}
}
return true;
}
}