/* * Copyright 2013-2016 microG Project Team * * 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 org.microg.gms.gcm; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.util.Log; import com.google.android.gms.iid.InstanceID; import java.io.IOException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static com.google.android.gms.gcm.GoogleCloudMessaging.ERROR_SERVICE_NOT_AVAILABLE; import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; import static org.microg.gms.common.Constants.GSF_PACKAGE_NAME; import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_REGISTER; import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_REGISTRATION; import static org.microg.gms.gcm.GcmConstants.ACTION_GCM_SEND; import static org.microg.gms.gcm.GcmConstants.EXTRA_APP; import static org.microg.gms.gcm.GcmConstants.EXTRA_ERROR; import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSAGE_ID; import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSENGER; import static org.microg.gms.gcm.GcmConstants.EXTRA_REGISTRATION_ID; import static org.microg.gms.gcm.GcmConstants.EXTRA_UNREGISTERED; import static org.microg.gms.gcm.GcmConstants.PERMISSION_GTALK; import static org.microg.gms.gcm.GcmConstants.PERMISSION_RECEIVE; public class CloudMessagingRpc { private static final AtomicInteger messageIdCounter = new AtomicInteger(1); private static String gcmPackageName; private final BlockingQueue<Intent> messengerResponseQueue = new LinkedBlockingQueue<Intent>(); private final Messenger messenger = new Messenger(new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { if (msg == null || !(msg.obj instanceof Intent)) { // Invalid message -> drop return; } Intent intent = (Intent) msg.obj; if (ACTION_C2DM_REGISTRATION.equals(intent.getAction())) { messengerResponseQueue.add(intent); } } }); /** * Due to it's nature of being a monitored reference, Intents can be used to authenticate a package source. */ private PendingIntent selfAuthIntent; private Context context; public CloudMessagingRpc(Context context) { this.context = context; } public static String getGcmPackageName(Context context) { if (gcmPackageName != null) { return gcmPackageName; } PackageManager packageManager = context.getPackageManager(); for (ResolveInfo resolveInfo : packageManager.queryIntentServices(new Intent(ACTION_C2DM_REGISTER), 0)) { if (packageManager.checkPermission(PERMISSION_RECEIVE, resolveInfo.serviceInfo.packageName) == PERMISSION_GRANTED) { return gcmPackageName = resolveInfo.serviceInfo.packageName; } } try { ApplicationInfo appInfo = packageManager.getApplicationInfo(GMS_PACKAGE_NAME, 0); return gcmPackageName = appInfo.packageName; } catch (PackageManager.NameNotFoundException ignored) { } try { ApplicationInfo appInfo = packageManager.getApplicationInfo(GSF_PACKAGE_NAME, 0); return gcmPackageName = appInfo.packageName; } catch (PackageManager.NameNotFoundException ex3) { return null; } } public void close() { // Cancel the authentication if (selfAuthIntent != null) { selfAuthIntent.cancel(); selfAuthIntent = null; } } private PendingIntent getSelfAuthIntent() { if (selfAuthIntent == null) { Intent intent = new Intent(); intent.setPackage("com.google.example.invalidpackage"); selfAuthIntent = PendingIntent.getBroadcast(context, 0, intent, 0); } return selfAuthIntent; } public Intent sendRegisterMessageBlocking(Bundle extras) throws IOException { sendRegisterMessage(extras); Intent resultIntent; try { resultIntent = messengerResponseQueue.poll(30, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new IOException(e.getMessage()); } if (resultIntent == null) { throw new IOException(ERROR_SERVICE_NOT_AVAILABLE); } return resultIntent; } private void sendRegisterMessage(Bundle extras) { Intent intent = new Intent(ACTION_C2DM_REGISTER); intent.setPackage(getGcmPackageName(context)); extras.putString(EXTRA_MESSAGE_ID, "google.rpc" + messageIdCounter.getAndIncrement()); intent.putExtras(extras); intent.putExtra(EXTRA_MESSENGER, messenger); intent.putExtra(EXTRA_APP, getSelfAuthIntent()); context.startService(intent); } public void sendGcmMessage(Bundle extras) { Intent intent = new Intent(ACTION_GCM_SEND); intent.setPackage(GMS_PACKAGE_NAME); intent.putExtras(extras); intent.putExtra(EXTRA_APP, getSelfAuthIntent()); context.sendOrderedBroadcast(intent, PERMISSION_GTALK); } public String handleRegisterMessageResult(Intent resultIntent) throws IOException { if (resultIntent == null) throw new IOException(InstanceID.ERROR_SERVICE_NOT_AVAILABLE); String result = resultIntent.getStringExtra(EXTRA_REGISTRATION_ID); if (result == null) result = resultIntent.getStringExtra(EXTRA_UNREGISTERED); if (result != null) return result; result = resultIntent.getStringExtra(EXTRA_ERROR); throw new IOException(result != null ? result : InstanceID.ERROR_SERVICE_NOT_AVAILABLE); } }