All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.android.apps.dashclock.api.DashClockExtension Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013 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.
 */

package com.google.android.apps.dashclock.api;

import com.google.android.apps.dashclock.api.internal.IExtension;
import com.google.android.apps.dashclock.api.internal.IExtensionHost;

import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.pm.Signature;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;

/**
 * Base class for a DashClock extension. Extensions are a way for other apps to show additional
 * status information within DashClock widgets that the user may add to the lockscreen or home
 * screen. A limited amount of status information is supported. See the {@link ExtensionData} class
 * for the types of information that can be displayed.
 *
 * 

Subclassing {@link DashClockExtension}

* * Subclasses must implement at least the {@link #onUpdateData(int)} method, which will be called * when DashClock requests updated data to show for this extension. Once the extension has new * data to show, call {@link #publishUpdate(ExtensionData)} to pass the data to the main DashClock * process. {@link #onUpdateData(int)} will by default be called roughly once per hour, but * extensions can use methods such as {@link #setUpdateWhenScreenOn(boolean)} and * {@link #addWatchContentUris(String[])} to request more frequent updates. * *

* Subclasses can also override the {@link #onInitialize(boolean)} method to perform basic * initialization each time a connection to DashClock is established or re-established. * *

Registering extensions

* An extension is simply a service that the DashClock process binds to. Subclasses of this * base {@link DashClockExtension} class should thus be declared as <service> * components in the application's AndroidManifest.xml file. * *

* The main DashClock app discovers available extensions using Android's {@link Intent} mechanism. * Ensure that your service definition includes an <intent-filter> * with an action of {@link #ACTION_EXTENSION}. Also make sure to require the * {@link #PERMISSION_READ_EXTENSION_DATA} permission so that only DashClock can bind to your * service and request updates. Lastly, there are a few <meta-data> elements that * you should add to your service definition: * *

    *
  • protocolVersion (required): should be 1.
  • *
  • description (required): should be a one- or two-sentence description * of the extension, as a string.
  • *
  • settingsActivity (optional): if present, should be the qualified * component name for a configuration activity in the extension's package that DashClock can offer * to the user for customizing the extension.
  • *
  • worldReadable (optional): if present and true (default is false), will allow * other apps besides DashClock to read data for this extension.
  • *
* *

Example

* * Below is an example extension declaration in the manifest: * *
 * <service android:name=".ExampleExtension"
 *     android:icon="@drawable/ic_extension_example"
 *     android:label="@string/extension_title"
 *     android:permission="com.google.android.apps.dashclock.permission.READ_EXTENSION_DATA">
 *     <intent-filter>
 *         <action android:name="com.google.android.apps.dashclock.Extension" />
 *     </intent-filter>
 *     <meta-data android:name="protocolVersion" android:value="2" />
 *     <meta-data android:name="worldReadable" android:value="true" />
 *     <meta-data android:name="description"
 *         android:value="@string/extension_description" />
 *     <!-- A settings activity is optional -->
 *     <meta-data android:name="settingsActivity"
 *         android:value=".ExampleSettingsActivity" />
 * </service>
 * 
* * If a settingsActivity meta-data element is present, an activity with the given * component name should be defined and exported in the application's manifest as well. DashClock * will set the {@link #EXTRA_FROM_DASHCLOCK_SETTINGS} extra to true in the launch intent for this * activity. An example is shown below: * *
 * <activity android:name=".ExampleSettingsActivity"
 *     android:label="@string/title_settings"
 *     android:exported="true" />
 * 
* * Finally, below is a simple example {@link DashClockExtension} subclass that shows static data in * DashClock: * *
 * public class ExampleExtension extends DashClockExtension {
 *     protected void onUpdateData(int reason) {
 *         publishUpdate(new ExtensionData()
 *                 .visible(true)
 *                 .icon(R.drawable.ic_extension_example)
 *                 .status("Hello")
 *                 .expandedTitle("Hello, world!")
 *                 .expandedBody("This is an example.")
 *                 .clickIntent(new Intent(Intent.ACTION_VIEW,
 *                         Uri.parse("http://www.google.com"))));
 *     }
 * }
 * 
*/ public abstract class DashClockExtension extends Service { private static final String TAG = "DashClockExtension"; /** * Indicates that {@link #onUpdateData(int)} was triggered for an unknown reason. This should * be treated as a generic update (similar to {@link #UPDATE_REASON_PERIODIC}. */ public static final int UPDATE_REASON_UNKNOWN = 0; /** * Indicates that this is the first call to {@link #onUpdateData(int)} since the connection to * the main DashClock app was established. Note that updates aren't requested in response to * reconnections after a connection is lost. */ public static final int UPDATE_REASON_INITIAL = 1; /** * Indicates that {@link #onUpdateData(int)} was triggered due to a normal perioidic refresh * of extension data. */ public static final int UPDATE_REASON_PERIODIC = 2; /** * Indicates that {@link #onUpdateData(int)} was triggered because settings for this extension * may have changed. */ public static final int UPDATE_REASON_SETTINGS_CHANGED = 3; /** * Indicates that {@link #onUpdateData(int)} was triggered because content changed on a content * URI previously registered with {@link #addWatchContentUris(String[])}. */ public static final int UPDATE_REASON_CONTENT_CHANGED = 4; /** * Indicates that {@link #onUpdateData(int)} was triggered because the device screen turned on * and the extension has called * {@link #setUpdateWhenScreenOn(boolean) setUpdateWhenScreenOn(true)}. */ public static final int UPDATE_REASON_SCREEN_ON = 5; /** * Indicates that {@link #onUpdateData(int)} was triggered because the user explicitly requested * that the extension be updated. * * @since Protocol Version 2 (API r2.x) */ public static final int UPDATE_REASON_MANUAL = 6; /** * The {@link Intent} action representing a DashClock extension. This service should * declare an <intent-filter> for this action in order to register with * DashClock. */ public static final String ACTION_EXTENSION = "com.google.android.apps.dashclock.Extension"; /** * Boolean extra that will be set to true when DashClock starts extension settings activities. * Check for this extra in your settings activity if you need to adjust your UI depending on * whether or not the user came from DashClock's settings screen. * * @since Protocol Version 2 (API r2.x) */ public static final String EXTRA_FROM_DASHCLOCK_SETTINGS = "com.google.android.apps.dashclock.extra.FROM_DASHCLOCK_SETTINGS"; /** * The permission that DashClock extensions should require callers to have before providing * any status updates. Permission checks are implemented automatically by the base class. */ public static final String PERMISSION_READ_EXTENSION_DATA = "com.google.android.apps.dashclock.permission.READ_EXTENSION_DATA"; /** * The protocol version with which the world readability option became available. * * @since Protocol Version 2 (API r2.x) */ private static final int PROTOCOL_VERSION_WORLD_READABILITY = 2; private boolean mInitialized = false; private boolean mIsWorldReadable = false; private IExtensionHost mHost; private volatile Looper mServiceLooper; private volatile Handler mServiceHandler; protected DashClockExtension() { super(); } @Override public void onCreate() { super.onCreate(); loadMetaData(); HandlerThread thread = new HandlerThread( "DashClockExtension:" + getClass().getSimpleName()); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new Handler(mServiceLooper); } @Override public void onDestroy() { mServiceHandler.removeCallbacksAndMessages(null); // remove all callbacks mServiceLooper.quit(); } private void loadMetaData() { PackageManager pm = getPackageManager(); try { ServiceInfo si = pm.getServiceInfo( new ComponentName(this, getClass()), PackageManager.GET_META_DATA); Bundle metaData = si.metaData; if (metaData != null) { int protocolVersion = metaData.getInt("protocolVersion"); mIsWorldReadable = protocolVersion >= PROTOCOL_VERSION_WORLD_READABILITY && metaData.getBoolean("worldReadable"); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Could not load metadata (e.g. world readable) for extension."); } } @Override public final IBinder onBind(Intent intent) { return mBinder; } private IExtension.Stub mBinder = new IExtension.Stub() { @Override public void onInitialize(IExtensionHost host, boolean isReconnect) throws RemoteException { if (!mIsWorldReadable) { // If not world readable, check the signature of the [first] package with the given // UID against the known-good official DashClock app signature. boolean verified = false; PackageManager pm = getPackageManager(); String[] packages = pm.getPackagesForUid(getCallingUid()); if (packages != null && packages.length > 0) { try { PackageInfo pi = pm.getPackageInfo(packages[0], PackageManager.GET_SIGNATURES); if (pi.signatures != null && pi.signatures.length == 1 && DASHCLOCK_SIGNATURE.equals(pi.signatures[0])) { verified = true; } } catch (PackageManager.NameNotFoundException ignored) { } } if (!verified) { Log.e(TAG, "Caller is not official DashClock app and this " + "extension is not world-readable."); throw new SecurityException("Caller is not official DashClock app and this " + "extension is not world-readable."); } } mHost = host; if (!mInitialized) { DashClockExtension.this.onInitialize(isReconnect); mInitialized = true; } } @Override public void onUpdate(final int reason) throws RemoteException { if (!mInitialized) { return; } // Do this in a separate thread mServiceHandler.post(new Runnable() { @Override public void run() { DashClockExtension.this.onUpdateData(reason); } }); } }; /** * Called when a connection with the main DashClock app has been established or re-established * after a previous one was lost. In this latter case, the parameter isReconnect * will be true. Override this method to perform basic extension initialization before calls * to {@link #onUpdateData(int)} are made. * * @param isReconnect Whether or not this call is being made after a connection was dropped and * a new connection has been established. */ protected void onInitialize(boolean isReconnect) { } /** * Called when the DashClock app process is requesting that the extension provide updated * information to show to the user. Implementations can choose to do nothing, or more commonly, * provide an update using the {@link #publishUpdate(ExtensionData)} method. Note that doing * nothing doesn't clear existing data. To clear any existing data, call * {@link #publishUpdate(ExtensionData)} with null data. * * @param reason The reason for the update. See {@link #UPDATE_REASON_PERIODIC} and related * constants for more details. */ protected abstract void onUpdateData(int reason); /** * Notifies the main DashClock app that new data is available for the extension and should * potentially be shown to the user. Note that this call does not necessarily need to be made * from inside the {@link #onUpdateData(int)} method, but can be made only after * {@link #onInitialize(boolean)} has been called. If you only call this from within * {@link #onUpdateData(int)} this is already ensured. * * @param data The data to show, or null if existing data should be cleared (hiding * the extension from view). */ protected final void publishUpdate(ExtensionData data) { try { mHost.publishUpdate(data); } catch (RemoteException e) { Log.e(TAG, "Couldn't publish updated extension data.", e); } } /** * Requests that the main DashClock app watch the given content URIs (using * {@link android.content.ContentResolver#registerContentObserver(android.net.Uri, boolean, * android.database.ContentObserver) ContentResolver.registerContentObserver}) * and call this extension's {@link #onUpdateData(int)} method when changes are observed. * This should generally be called in the {@link #onInitialize(boolean)} method. * * @param uris The URIs to watch. */ protected final void addWatchContentUris(String[] uris) { try { mHost.addWatchContentUris(uris); } catch (RemoteException e) { Log.e(TAG, "Couldn't watch content URIs.", e); } } /** * Requests that the main DashClock app stop watching all content URIs previously registered * with {@link #addWatchContentUris(String[])} for this extension. * * @param uris The URIs to watch. * @since Protocol Version 2 (API r2.x) */ protected final void removeAllWatchContentUris() { try { mHost.removeAllWatchContentUris(); } catch (RemoteException e) { Log.e(TAG, "Couldn't stop watching content URIs.", e); } } /** * Requests that the main DashClock app call (or not call) this extension's * {@link #onUpdateData(int)} method when the screen turns on (the phone resumes from idle). * This should generally be called in the {@link #onInitialize(boolean)} method. By default, * extensions do not get updated when the screen turns on. * * @see Intent#ACTION_SCREEN_ON * @param updateWhenScreenOn Whether or not a call to {@link #onUpdateData(int)} method when * the screen turns on. */ protected final void setUpdateWhenScreenOn(boolean updateWhenScreenOn) { try { mHost.setUpdateWhenScreenOn(updateWhenScreenOn); } catch (RemoteException e) { Log.e(TAG, "Couldn't set the extension to update upon ACTION_SCREEN_ON.", e); } } /** * The signature of the official DashClock app (net.nurik.roman.dashclock). Used to * compare caller when {@link #mIsWorldReadable} is false. */ private static final Signature DASHCLOCK_SIGNATURE = new Signature("" + "308203523082023aa00302010202044c1132a9300d06092a864886f70d0101050500306b310b30090603" + "550406130255533110300e06035504081307556e6b6e6f776e3110300e06035504071307556e6b6e6f77" + "6e3110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e3114301206" + "03550403130b526f6d616e204e7572696b301e170d3130303631303138343435375a170d333731303236" + "3138343435375a306b310b30090603550406130255533110300e06035504081307556e6b6e6f776e3110" + "300e06035504071307556e6b6e6f776e3110300e060355040a1307556e6b6e6f776e3110300e06035504" + "0b1307556e6b6e6f776e311430120603550403130b526f6d616e204e7572696b30820122300d06092a86" + "4886f70d01010105000382010f003082010a02820101008906222723a4b30dca6f0702b041e6f361e38e" + "35105ec530bf43f4f1786737fefe6ccfa3b038a3700ea685dd185112a0a8f96327d3373de28e05859a87" + "bde82372baed5618082121d6946e4affbdfb6771abb782147d58a2323518b34efcce144ec3e45fb2556e" + "ba1c40b42ccbcc1266c9469b5447edf09d5cf8e2ed62cfb3bd902e47f48a11a815a635c3879c882eae92" + "3c7f73bfba4039b7c19930617e3326fa163b924eda398bacc0d6ef8643a32223ce1d767734e866553ad5" + "0d11fb22ac3a15ba021a6a3904a95ed65f54142256cb0db90038dd55adfeeb18d3ffb085c4380817268f" + "039119ecbdfca843e4b82209947fd88470b3d8c76fc15878fbc4f10203010001300d06092a864886f70d" + "0101050500038201010047063efdd5011adb69cca6461a57443fef59243f85e5727ec0d67513bb04b650" + "b1144fc1f54e09789c278171c52b9305a7265cafc13b89d91eb37ddce34a5c1f17c8c36f86c957c4e9ca" + "cc19e6822e0a5711f2cfba2c5913ba582ab69485548b13072bc736310b9da85a716d0418e6449450ceda" + "dfc1c897f93ed6189cfa0a02b893125bd4b1c4e4dd50c1ad33e221120b8488841763a3361817081e7691" + "1e76d3adcf94b23c758ceb955f9fdf8ef4a8351fc279867a25729f081b511209e96dfa8520225b810072" + "de5e8eefc1a6cc22f46857e2cc4fd1a1eaac76054f34352b63c9d53691515b42cc771f195343e61397cb" + "7b04ada2a627410d29c214976d13"); }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy