简体   繁体   中英

SERVICE_NOT_AVAILABLE error while using c2dm

I am trying to get C2DM to work, I'm following the ChrometoPhone example here When my emulator tries to register to C2DM, I get SERVICE_NOT_AVAILABLE . I have checked that the emulator has a gmail account set up and the same account is being passed as senderId from DeviceRegistrar.java in the example code. I am also sure that my emulator can access the Internet and I have all permissions (to access the Internet, wake lock etc.). What else can be wrong here?

I'm not sure if I did a good job of explaining the problem. Please let me know if I need to explain something else.

Here is C2DMBaseReceiver.java

package com.apps.terrapin;

import java.io.IOException;

import android.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.util.Log;

/**
 * Base class for C2D message receiver. Includes constants for the
 * strings used in the protocol.
 */
public abstract class C2DMBaseReceiver extends IntentService {
    private static final String C2DM_RETRY = "com.google.android.c2dm.intent.RETRY";

    public static final String REGISTRATION_CALLBACK_INTENT = "com.google.android.c2dm.intent.REGISTRATION";
    private static final String C2DM_INTENT = "com.google.android.c2dm.intent.RECEIVE";

    // Logging tag
    private static final String TAG = "C2DM";

    // Extras in the registration callback intents.
    public static final String EXTRA_UNREGISTERED = "unregistered";

    public static final String EXTRA_ERROR = "error";

    public static final String EXTRA_REGISTRATION_ID = "registration_id";

    public static final String ERR_SERVICE_NOT_AVAILABLE = "SERVICE_NOT_AVAILABLE";
    public static final String ERR_ACCOUNT_MISSING = "ACCOUNT_MISSING";
    public static final String ERR_AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED";
    public static final String ERR_TOO_MANY_REGISTRATIONS = "TOO_MANY_REGISTRATIONS";
    public static final String ERR_INVALID_PARAMETERS = "INVALID_PARAMETERS";
    public static final String ERR_INVALID_SENDER = "INVALID_SENDER";
    public static final String ERR_PHONE_REGISTRATION_ERROR = "PHONE_REGISTRATION_ERROR";

    // wakelock
    private static final String WAKELOCK_KEY = "C2DM_LIB";

    private static PowerManager.WakeLock mWakeLock;
    private final String senderId;

    /**
     * The C2DMReceiver class must create a no-arg constructor and pass the 
     * sender id to be used for registration.
     */
    public C2DMBaseReceiver(String senderId) {
        // senderId is used as base name for threads, etc.
        super(senderId);
        this.senderId = senderId;
    }

    /**
     * Called when a cloud message has been received.
     */
    protected abstract void onMessage(Context context, Intent intent);

    /**
     * Called on registration error. Override to provide better
     * error messages.
     *  
     * This is called in the context of a Service - no dialog or UI.
     */
    public abstract void onError(Context context, String errorId);

    /**
     * Called when a registration token has been received.
     */
    public void onRegistered(Context context, String registrationId) throws IOException {
        // registrationId will also be saved
    }

    /**
     * Called when the device has been unregistered.
     */
    public void onUnregistered(Context context) {
    }


    @Override
    public final void onHandleIntent(Intent intent) {
        try {
            Context context = getApplicationContext();
            if (intent.getAction().equals(REGISTRATION_CALLBACK_INTENT)) {
                handleRegistration(context, intent);
            } else if (intent.getAction().equals(C2DM_INTENT)) {
                onMessage(context, intent);
            } else if (intent.getAction().equals(C2DM_RETRY)) {
                C2DMessaging.register(context, senderId);
            }
        } finally {
            //  Release the power lock, so phone can get back to sleep.
            // The lock is reference counted by default, so multiple 
            // messages are ok.

            // If the onMessage() needs to spawn a thread or do something else,
            // it should use it's own lock.
            mWakeLock.release();
        }
    }


    /**
     * Called from the broadcast receiver. 
     * Will process the received intent, call handleMessage(), registered(), etc.
     * in background threads, with a wake lock, while keeping the service 
     * alive. 
     */
    static void runIntentInService(Context context, Intent intent) {
        if (mWakeLock == null) {
            // This is called from BroadcastReceiver, there is no init.
            PowerManager pm = 
                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 
                    WAKELOCK_KEY);
        }
        mWakeLock.acquire();

        // Use a naming convention, similar with how permissions and intents are 
        // used. Alternatives are introspection or an ugly use of statics. 
        String receiver = context.getPackageName() + ".C2DMReceiver";
        intent.setClassName(context, receiver);

        context.startService(intent);

    }


    private void handleRegistration(final Context context, Intent intent) {
        final String registrationId = intent.getStringExtra(EXTRA_REGISTRATION_ID);
        String error = intent.getStringExtra(EXTRA_ERROR);
        String removed = intent.getStringExtra(EXTRA_UNREGISTERED);

        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "dmControl: registrationId = " + registrationId +
                ", error = " + error + ", removed = " + removed);
        }

        if (removed != null) {
            // Remember we are unregistered
            C2DMessaging.clearRegistrationId(context);
            onUnregistered(context);
            return;
        } else if (error != null) {
            // we are not registered, can try again
            C2DMessaging.clearRegistrationId(context);
            // Registration failed
            Log.e(TAG, "Registration error " + error);
            onError(context, error);
            if ("SERVICE_NOT_AVAILABLE".equals(error)) {
                long backoffTimeMs = C2DMessaging.getBackoff(context);

                Log.d(TAG, "Scheduling registration retry, backoff = " + backoffTimeMs);
                Intent retryIntent = new Intent(C2DM_RETRY);
                PendingIntent retryPIntent = PendingIntent.getBroadcast(context, 
                        0 /*requestCode*/, retryIntent, 0 /*flags*/);

                AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
                am.set(AlarmManager.ELAPSED_REALTIME,
                        backoffTimeMs, retryPIntent);

                // Next retry should wait longer.
                backoffTimeMs *= 2;
                C2DMessaging.setBackoff(context, backoffTimeMs);
            } 
        } else {
            try {
                onRegistered(context, registrationId);
                C2DMessaging.setRegistrationId(context, registrationId);
            } catch (IOException ex) {
                Log.e(TAG, "Registration error " + ex.getMessage());
            }
        }
    }
}

C2DMBroadcastReceiver.java

package com.apps.terrapin;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

/**
 * Helper class to handle BroadcastReciver behavior.
 * - can only run for a limited amount of time - it must start a real service 
 * for longer activity
 * - must get the power lock, must make sure it's released when all done.
 * 
 */
public class C2DMBroadcastReceiver extends BroadcastReceiver {

    @Override
    public final void onReceive(Context context, Intent intent) {
        // To keep things in one place.
        C2DMBaseReceiver.runIntentInService(context, intent);
        setResult(Activity.RESULT_OK, null /* data */, null /* extra */);

        if (intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")) {
            Log.e("TAG", "Broadcast receiver got REGISTRATION");
            Log.e("TAG", "ID: " + intent.getExtras().getString("registration_id"));

        } else if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")) {
            Log.e("TAG", "Broadcast receiver got message");
         }
    }
}

C2DMessaging.java

package com.apps.terrapin;

/*
 * Copyright 2010 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.
 */

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;

/**
 * Utilities for device registration.
 *
 * Will keep track of the registration token in a private preference.
 */
public class C2DMessaging {
    public static final String EXTRA_SENDER = "sender";
    public static final String EXTRA_APPLICATION_PENDING_INTENT = "app";
    public static final String REQUEST_UNREGISTRATION_INTENT = "com.google.android.c2dm.intent.UNREGISTER";
    public static final String REQUEST_REGISTRATION_INTENT = "com.google.android.c2dm.intent.REGISTER";
    public static final String LAST_REGISTRATION_CHANGE = "last_registration_change";
    public static final String BACKOFF = "backoff";
    public static final String GSF_PACKAGE = "com.google.android.gsf";


    // package
    static final String PREFERENCE = "com.google.android.c2dm";

    private static final long DEFAULT_BACKOFF = 30000;

    /**
     * Initiate c2d messaging registration for the current application
     */
    public static void register(Context context, String senderId) {
        Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");
        //registrationIntent.setPackage(GSF_PACKAGE);
        registrationIntent.putExtra("app", PendingIntent.getBroadcast(context, 0, new Intent(), 0));
        registrationIntent.putExtra("sender", senderId);
        context.startService(registrationIntent);
    }

    /**
     * Unregister the application. New messages will be blocked by server.
     */
    public static void unregister(Context context) {
        Intent regIntent = new Intent(REQUEST_UNREGISTRATION_INTENT);
        regIntent.setPackage(GSF_PACKAGE);
        regIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT, PendingIntent.getBroadcast(context,
                0, new Intent(), 0));
        context.startService(regIntent);
    }

    /**
     * Return the current registration id.
     *
     * If result is empty, the registration has failed.
     *
     * @return registration id, or empty string if the registration is not complete.
     */
    public static String getRegistrationId(Context context) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        String registrationId = prefs.getString("dm_registration", "");
        return registrationId;
    }

    public static long getLastRegistrationChange(Context context) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        return prefs.getLong(LAST_REGISTRATION_CHANGE, 0);
    }

    static long getBackoff(Context context) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        return prefs.getLong(BACKOFF, DEFAULT_BACKOFF);
    }

    static void setBackoff(Context context, long backoff) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        Editor editor = prefs.edit();
        editor.putLong(BACKOFF, backoff);
        editor.commit();

    }

    // package
    static void clearRegistrationId(Context context) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        Editor editor = prefs.edit();
        editor.putString("dm_registration", "");
        editor.putLong(LAST_REGISTRATION_CHANGE, System.currentTimeMillis());
        editor.commit();

    }

    // package
    static void setRegistrationId(Context context, String registrationId) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        Editor editor = prefs.edit();
        editor.putString("dm_registration", registrationId);
        editor.commit();

    }
}

C2DMReceiver.java

package com.apps.terrapin;

/*
 * Copyright 2010 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.
 */

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;

public class C2DMReceiver extends C2DMBaseReceiver {
    public C2DMReceiver() {
        super(DeviceRegistrar.SENDER_ID);
    }

    @Override
    public void onRegistered(Context context, String registration) {
        DeviceRegistrar.registerWithServer(context, registration);
    }

    @Override
    public void onUnregistered(Context context) {
        SharedPreferences prefs = Prefs.get(context);
        String deviceRegistrationID = prefs.getString("deviceRegistrationID",
                null);
        DeviceRegistrar.unregisterWithServer(context, deviceRegistrationID);
    }

    @Override
    public void onError(Context context, String errorId) {
        Log.e("TAG", "Error ocurred in onError");
    }

    @Override
    public void onMessage(Context context, Intent intent) {
        Bundle extras = intent.getExtras();
        String msg = extras.getString("message");

        Log.e("TAG", "Got a message from cloud: " + msg);
    }
}

DeviceRegistrar.java

package com.apps.terrapin;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.content.Context;
import android.util.Log;

public class DeviceRegistrar {
    static final String SENDER_ID = "sender@gmail.com";

    public static void registerWithServer(final Context context, final String deviceRegistrationID) {
        HttpClient client = new DefaultHttpClient();
        HttpGet request = new HttpGet();

        String url = "http://" + "192.168.1.11" + "/message.php?";
        url += "device=" + deviceRegistrationID;

        try {
            request.setURI(new URI(url));
            HttpResponse response = client.execute(request);
            Log.d("@@@@@ Server response @@@@@", response.toString());
        } catch (URISyntaxException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void unregisterWithServer(final Context context, final String deviceRegistrationID) {

    }
}

Manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:installLocation="preferExternal"
    package="com.apps.terrapin"
    android:versionCode="1"
    android:versionName="1.0" >
    <!-- Only this application can receive the messages and registration result -->    
    <permission android:name="com.apps.terrapin.permission.C2D_MESSAGE" 
        android:protectionLevel="signature" />
    <uses-permission android:name="com.apps.terrapin.permission.C2D_MESSAGE" />
    <!-- This app has permission to register and receive data message -->
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

    <!-- Permissions for internet access and account access -->
    <uses-permission android:name="android.permission.INTERNET" />

    <!-- App must have this permission to use the library -->
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <application
        android:icon="@drawable/earth"
        android:label="@string/app_name" >
        <activity
            android:label="@string/app_name"
            android:name=".TerraPin" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".TakePictureActivity" />
        <activity android:name=".SearchActivity" />
        <activity android:name=".SettingsActivity" />

        <uses-library android:name="com.google.android.maps" />

        <service android:name=".C2DMReceiver" />

        <!-- Only google service can send data messages for the app. If permission is not set -
             any other app can generate it --> 
        <receiver android:name="com.apps.terrapin.C2DMBroadcastReceiver"
                  android:permission="com.google.android.c2dm.permission.SEND">
            <!-- Receive the actual message -->
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category android:name="com.apps.terrapin" />
            </intent-filter>
            <!-- Receive the registration id -->
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <category android:name="com.apps.terrapin" />
            </intent-filter>
        </receiver>

    </application>

    <uses-sdk android:minSdkVersion="10" />

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

</manifest>

The error returned is SERVICE_NOT_AVAILABLE

In my main activity, I am doing this:

C2DMessaging.register(this, DeviceRegistrar.SENDER_ID);
        String regId = C2DMessaging.getRegistrationId(this);
        if (regId != null && !"".equals(regId)) {
            DeviceRegistrar.registerWithServer(this, regId);
        } else {
            C2DMessaging.register(this, DeviceRegistrar.SENDER_ID);
        }

In your Android Virtual Device Manager edit your Android Virtual Device (AVD) and set the target "Google APIs (Google Inc.) - API Level 8" instead of "Android 2.2 - API Level 8". After doing that run your emulator and in the settings set your google account (put the whitelisted email).

This solved the problem for me.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM