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 final Intent serviceIntent = new Intent(context, XmppConnectionService.class);
106 serviceIntent.setAction(XmppConnectionService.ACTION_RENEW_UNIFIED_PUSH_ENDPOINTS);
107 serviceIntent.putExtra("instance", instance);
108 serviceIntent.putExtra("application", application);
109 if (messenger instanceof Messenger) {
110 serviceIntent.putExtra("messenger", messenger);
111 }
112 Compatibility.startService(context, serviceIntent);
113 } else {
114 Log.d(Config.LOGTAG, "not successful. sending error message back to application");
115 final Intent registrationFailed = new Intent(ACTION_REGISTRATION_FAILED);
116 registrationFailed.setPackage(application);
117 registrationFailed.putExtra("token", instance);
118 context.sendBroadcast(registrationFailed);
119 }
120 } else {
121 Log.d(
122 Config.LOGTAG,
123 "ignoring invalid UnifiedPush registration. Unknown application "
124 + application);
125 }
126 }
127
128 private List<String> getBroadcastReceivers(final Context context, final String application) {
129 final Intent messageIntent = new Intent(ACTION_MESSAGE);
130 messageIntent.setPackage(application);
131 final List<ResolveInfo> resolveInfo =
132 context.getPackageManager().queryBroadcastReceivers(messageIntent, 0);
133 return Lists.transform(
134 resolveInfo, ri -> ri.activityInfo == null ? null : ri.activityInfo.packageName);
135 }
136
137 private void unregister(final Context context, final String instance) {
138 if (Strings.isNullOrEmpty(instance)) {
139 Log.w(Config.LOGTAG, "ignoring invalid UnifiedPush un-registration");
140 return;
141 }
142 final UnifiedPushDatabase unifiedPushDatabase = UnifiedPushDatabase.getInstance(context);
143 if (unifiedPushDatabase.deleteInstance(instance)) {
144 Log.d(Config.LOGTAG, "successfully removed " + instance + " from UnifiedPush");
145 }
146 }
147
148 private void unregisterApplication(final Context context, final Uri uri) {
149 if (uri != null && "package".equalsIgnoreCase(uri.getScheme())) {
150 final String application = uri.getSchemeSpecificPart();
151 if (Strings.isNullOrEmpty(application)) {
152 return;
153 }
154 Log.d(Config.LOGTAG, "app " + application + " has been removed from the system");
155 final UnifiedPushDatabase database = UnifiedPushDatabase.getInstance(context);
156 if (database.deleteApplication(application)) {
157 Log.d(Config.LOGTAG, "successfully removed " + application + " from UnifiedPush");
158 }
159 }
160 }
161
162 public static String hash(String... components) {
163 return BaseEncoding.base64()
164 .encode(
165 Hashing.sha256()
166 .hashString(Joiner.on('\0').join(components), Charsets.UTF_8)
167 .asBytes());
168 }
169}