1package eu.siacs.conversations.services;
2
3import android.app.PendingIntent;
4import android.content.BroadcastReceiver;
5import android.content.Context;
6import android.content.Intent;
7import android.content.pm.ResolveInfo;
8import android.net.Uri;
9import android.os.Messenger;
10import android.os.Parcelable;
11import android.util.Log;
12
13import com.google.common.base.Charsets;
14import com.google.common.base.Joiner;
15import com.google.common.base.Strings;
16import com.google.common.collect.Lists;
17import com.google.common.hash.Hashing;
18import com.google.common.io.BaseEncoding;
19
20import java.util.Arrays;
21import java.util.Collection;
22import java.util.List;
23
24import eu.siacs.conversations.Config;
25import eu.siacs.conversations.persistance.UnifiedPushDatabase;
26import eu.siacs.conversations.utils.Compatibility;
27
28public class UnifiedPushDistributor extends BroadcastReceiver {
29
30 public static final String ACTION_REGISTER = "org.unifiedpush.android.distributor.REGISTER";
31 public static final String ACTION_UNREGISTER = "org.unifiedpush.android.distributor.UNREGISTER";
32 public static final String ACTION_BYTE_MESSAGE =
33 "org.unifiedpush.android.distributor.feature.BYTES_MESSAGE";
34 public static final String ACTION_REGISTRATION_FAILED =
35 "org.unifiedpush.android.connector.REGISTRATION_FAILED";
36 public static final String ACTION_MESSAGE = "org.unifiedpush.android.connector.MESSAGE";
37 public static final String ACTION_NEW_ENDPOINT =
38 "org.unifiedpush.android.connector.NEW_ENDPOINT";
39
40 public static final String PREFERENCE_ACCOUNT = "up_push_account";
41 public static final String PREFERENCE_PUSH_SERVER = "up_push_server";
42
43 public static final List<String> PREFERENCES =
44 Arrays.asList(PREFERENCE_ACCOUNT, PREFERENCE_PUSH_SERVER);
45
46 @Override
47 public void onReceive(final Context context, final Intent intent) {
48 if (intent == null) {
49 return;
50 }
51 final String action = intent.getAction();
52 final String application;
53 final Parcelable appByPendingIntent = intent.getParcelableExtra("app");
54 if (appByPendingIntent instanceof PendingIntent) {
55 final PendingIntent pendingIntent = (PendingIntent) appByPendingIntent;
56 application = pendingIntent.getIntentSender().getCreatorPackage();
57 Log.d(Config.LOGTAG,"received application name via pending intent "+ application);
58 } else {
59 application = intent.getStringExtra("application");
60 }
61 final Parcelable messenger = intent.getParcelableExtra("messenger");
62 final String instance = intent.getStringExtra("token");
63 final List<String> features = intent.getStringArrayListExtra("features");
64 switch (Strings.nullToEmpty(action)) {
65 case ACTION_REGISTER:
66 register(context, application, instance, features, messenger);
67 break;
68 case ACTION_UNREGISTER:
69 unregister(context, instance);
70 break;
71 case Intent.ACTION_PACKAGE_FULLY_REMOVED:
72 unregisterApplication(context, intent.getData());
73 break;
74 default:
75 Log.d(Config.LOGTAG, "UnifiedPushDistributor received unknown action " + action);
76 break;
77 }
78 }
79
80 private void register(
81 final Context context,
82 final String application,
83 final String instance,
84 final Collection<String> features,
85 final Parcelable messenger) {
86 if (Strings.isNullOrEmpty(application) || Strings.isNullOrEmpty(instance)) {
87 Log.w(Config.LOGTAG, "ignoring invalid UnifiedPush registration");
88 return;
89 }
90 final List<String> receivers = getBroadcastReceivers(context, application);
91 if (receivers.contains(application)) {
92 final boolean byteMessage = features != null && features.contains(ACTION_BYTE_MESSAGE);
93 Log.d(
94 Config.LOGTAG,
95 "received up registration from "
96 + application
97 + "/"
98 + instance
99 + " features: "
100 + features);
101 if (UnifiedPushDatabase.getInstance(context).register(application, instance)) {
102 Log.d(
103 Config.LOGTAG,
104 "successfully created UnifiedPush entry. waking up XmppConnectionService");
105 quickLog(context, String.format("successfully registered %s (token = %s) for UnifiedPushed", application, instance));
106 final Intent serviceIntent = new Intent(context, XmppConnectionService.class);
107 serviceIntent.setAction(XmppConnectionService.ACTION_RENEW_UNIFIED_PUSH_ENDPOINTS);
108 serviceIntent.putExtra("instance", instance);
109 serviceIntent.putExtra("application", application);
110 if (messenger instanceof Messenger) {
111 serviceIntent.putExtra("messenger", messenger);
112 }
113 Compatibility.startService(context, serviceIntent);
114 } else {
115 Log.d(Config.LOGTAG, "not successful. sending error message back to application");
116 final Intent registrationFailed = new Intent(ACTION_REGISTRATION_FAILED);
117 registrationFailed.setPackage(application);
118 registrationFailed.putExtra("token", instance);
119 context.sendBroadcast(registrationFailed);
120 }
121 } else {
122 Log.d(
123 Config.LOGTAG,
124 "ignoring invalid UnifiedPush registration. Unknown application "
125 + application);
126 }
127 }
128
129 private List<String> getBroadcastReceivers(final Context context, final String application) {
130 final Intent messageIntent = new Intent(ACTION_MESSAGE);
131 messageIntent.setPackage(application);
132 final List<ResolveInfo> resolveInfo =
133 context.getPackageManager().queryBroadcastReceivers(messageIntent, 0);
134 return Lists.transform(
135 resolveInfo, ri -> ri.activityInfo == null ? null : ri.activityInfo.packageName);
136 }
137
138 private void unregister(final Context context, final String instance) {
139 if (Strings.isNullOrEmpty(instance)) {
140 Log.w(Config.LOGTAG, "ignoring invalid UnifiedPush un-registration");
141 return;
142 }
143 final UnifiedPushDatabase unifiedPushDatabase = UnifiedPushDatabase.getInstance(context);
144 if (unifiedPushDatabase.deleteInstance(instance)) {
145 quickLog(context, String.format("successfully unregistered token %s from UnifiedPushed (application requested unregister)", instance));
146 Log.d(Config.LOGTAG, "successfully removed " + instance + " from UnifiedPush");
147 }
148 }
149
150 private void unregisterApplication(final Context context, final Uri uri) {
151 if (uri != null && "package".equalsIgnoreCase(uri.getScheme())) {
152 final String application = uri.getSchemeSpecificPart();
153 if (Strings.isNullOrEmpty(application)) {
154 return;
155 }
156 Log.d(Config.LOGTAG, "app " + application + " has been removed from the system");
157 final UnifiedPushDatabase database = UnifiedPushDatabase.getInstance(context);
158 if (database.deleteApplication(application)) {
159 quickLog(context, String.format("successfully removed %s from UnifiedPushed (ACTION_PACKAGE_FULLY_REMOVED)", application));
160 Log.d(Config.LOGTAG, "successfully removed " + application + " from UnifiedPush");
161 }
162 }
163 }
164
165 public static String hash(String... components) {
166 return BaseEncoding.base64()
167 .encode(
168 Hashing.sha256()
169 .hashString(Joiner.on('\0').join(components), Charsets.UTF_8)
170 .asBytes());
171 }
172
173 public static void quickLog(final Context context, final String message) {
174 final Intent intent = new Intent(context, XmppConnectionService.class);
175 intent.setAction(XmppConnectionService.ACTION_QUICK_LOG);
176 intent.putExtra("message", message);
177 context.startService(intent);
178 }
179}