Merge "Ask the client to make itself known when it's not"

main
Jean Chalard 2013-04-01 09:00:25 +00:00 committed by Android (Google) Code Review
commit 43341ba042
8 changed files with 134 additions and 10 deletions

View File

@ -95,6 +95,12 @@
</intent-filter>
</receiver>
<receiver android:name=".DictionaryPackInstallBroadcastReceiver">
<intent-filter>
<action android:name="com.android.inputmethod.dictionarypack.UNKNOWN_CLIENT" />
</intent-filter>
</receiver>
<provider android:name="com.android.inputmethod.dictionarypack.DictionaryProvider"
android:grantUriPermissions="true"
android:exported="false"

View File

@ -24,17 +24,35 @@ package com.android.inputmethod.dictionarypack;
* is needed in particular to cleanly compile regression tests.
*/
public class DictionaryPackConstants {
/**
* The root domain for the dictionary pack, upon which authorities and actions will append
* their own distinctive strings.
*/
private static final String DICTIONARY_DOMAIN = "com.android.inputmethod.dictionarypack";
/**
* Authority for the ContentProvider protocol.
*/
// TODO: find some way to factorize this string with the one in the resources
public static final String AUTHORITY = "com.android.inputmethod.dictionarypack.aosp";
public static final String AUTHORITY = DICTIONARY_DOMAIN + ".aosp";
/**
* The action of the intent for publishing that new dictionary data is available.
*/
// TODO: make this different across different packages. A suggested course of action is
// to use the package name inside this string.
public static final String NEW_DICTIONARY_INTENT_ACTION =
"com.android.inputmethod.dictionarypack.newdict";
// NOTE: The appended string should be uppercase like all other actions, but it's not for
// historical reasons.
public static final String NEW_DICTIONARY_INTENT_ACTION = DICTIONARY_DOMAIN + ".newdict";
/**
* The action of the intent sent by the dictionary pack to ask for a client to make
* itself known. This is used when the settings activity is brought up for a client the
* dictionary pack does not know about.
*/
public static final String UNKNOWN_DICTIONARY_PROVIDER_CLIENT = DICTIONARY_DOMAIN
+ ".UNKNOWN_CLIENT";
// In the above intents, the name of the string extra that contains the name of the client
// we want information about.
public static final String DICTIONARY_PROVIDER_CLIENT_EXTRA = "client";
}

View File

@ -509,6 +509,11 @@ public final class DictionaryProvider extends ContentProvider {
} catch (final BadFormatException e) {
Log.w(TAG, "Not enough information to insert this dictionary " + values, e);
}
// We just received new information about the list of dictionary for this client.
// For all intents and purposes, this is new metadata, so we should publish it
// so that any listeners (like the Settings interface for example) can update
// themselves.
UpdateHandler.publishUpdateMetadataCompleted(getContext(), true);
break;
case DICTIONARY_V1_WHOLE_LIST:
case DICTIONARY_V1_DICT_INFO:

View File

@ -110,6 +110,15 @@ public final class DictionarySettingsFragment extends PreferenceFragment
super.onResume();
mChangedSettings = false;
UpdateHandler.registerUpdateEventListener(this);
final Activity activity = getActivity();
if (!MetadataDbHelper.isClientKnown(activity, mClientId)) {
Log.i(TAG, "Unknown dictionary pack client: " + mClientId + ". Requesting info.");
final Intent unknownClientBroadcast =
new Intent(DictionaryPackConstants.UNKNOWN_DICTIONARY_PROVIDER_CLIENT);
unknownClientBroadcast.putExtra(
DictionaryPackConstants.DICTIONARY_PROVIDER_CLIENT_EXTRA, mClientId);
activity.sendBroadcast(unknownClientBroadcast);
}
final IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
getActivity().registerReceiver(mConnectivityChangedReceiver, filter);
@ -363,7 +372,12 @@ public final class DictionarySettingsFragment extends PreferenceFragment
getActivity(), android.R.anim.fade_out));
preferenceView.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_in));
mUpdateNowMenu.setTitle(R.string.check_for_updates_now);
// The menu is created by the framework asynchronously after the activity,
// which means it's possible to have the activity running but the menu not
// created yet - hence the necessity for a null check here.
if (null != mUpdateNowMenu) {
mUpdateNowMenu.setTitle(R.string.check_for_updates_now);
}
}
});
}

View File

@ -16,13 +16,9 @@
package com.android.inputmethod.dictionarypack;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.R;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public final class EventHandler extends BroadcastReceiver {
private static final String TAG = EventHandler.class.getName();

View File

@ -444,7 +444,19 @@ public final class UpdateHandler {
manager.remove(fileId);
}
private static void publishUpdateMetadataCompleted(final Context context,
/**
* Sends a broadcast informing listeners that the dictionaries were updated.
*
* This will call all local listeners through the UpdateEventListener#downloadedMetadata
* callback (for example, the dictionary provider interface uses this to stop the Loading
* animation) and send a broadcast about the metadata having been updated. For a client of
* the dictionary pack like Latin IME, this means it should re-query the dictionary pack
* for any relevant new data.
*
* @param context the context, to send the broadcast.
* @param downloadSuccessful whether the download of the metadata was successful or not.
*/
public static void publishUpdateMetadataCompleted(final Context context,
final boolean downloadSuccessful) {
// We need to warn all listeners of what happened. But some listeners may want to
// remove themselves or re-register something in response. Hence we should take a

View File

@ -450,4 +450,25 @@ public final class BinaryDictionaryFileDumper {
info.toContentValues());
}
}
/**
* Initialize a client record with the dictionary content provider.
*
* This merely acquires the content provider and calls
* #reinitializeClientRecordInDictionaryContentProvider.
*
* @param context the context for resources and providers.
* @param clientId the client ID to use.
*/
public static void initializeClientRecordHelper(final Context context,
final String clientId) {
try {
final ContentProviderClient client = context.getContentResolver().
acquireContentProviderClient(getProviderUriBuilder("").build());
if (null == client) return;
reinitializeClientRecordInDictionaryContentProvider(context, client, clientId);
} catch (RemoteException e) {
Log.e(TAG, "Cannot contact the dictionary content provider", e);
}
}
}

View File

@ -25,14 +25,35 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.util.Log;
/**
* Takes action to reload the necessary data when a dictionary pack was added/removed.
* Receives broadcasts pertaining to dictionary management and takes the appropriate action.
*
* This object receives three types of broadcasts.
* - Package installed/added. When a dictionary provider application is added or removed, we
* need to query the dictionaries.
* - New dictionary broadcast. The dictionary provider broadcasts new dictionary availability. When
* this happens, we need to re-query the dictionaries.
* - Unknown client. If the dictionary provider is in urgent need of data about some client that
* it does not know, it sends this broadcast. When we receive this, we need to tell the dictionary
* provider about ourselves. This happens when the settings for the dictionary pack are accessed,
* but Latin IME never got a chance to register itself.
*/
public final class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = DictionaryPackInstallBroadcastReceiver.class.getSimpleName();
final LatinIME mService;
public DictionaryPackInstallBroadcastReceiver() {
// This empty constructor is necessary for the system to instantiate this receiver.
// This happens when the dictionary pack says it can't find a record for our client,
// which happens when the dictionary pack settings are called before the keyboard
// was ever started once.
Log.i(TAG, "Latin IME dictionary broadcast receiver instantiated from the framework.");
mService = null;
}
public DictionaryPackInstallBroadcastReceiver(final LatinIME service) {
mService = service;
}
@ -44,6 +65,11 @@ public final class DictionaryPackInstallBroadcastReceiver extends BroadcastRecei
// We need to reread the dictionary if a new dictionary package is installed.
if (action.equals(Intent.ACTION_PACKAGE_ADDED)) {
if (null == mService) {
Log.e(TAG, "Called with intent " + action + " but we don't know the service: this "
+ "should never happen");
return;
}
final Uri packageUri = intent.getData();
if (null == packageUri) return; // No package name : we can't do anything
final String packageName = packageUri.getSchemeSpecificPart();
@ -71,6 +97,11 @@ public final class DictionaryPackInstallBroadcastReceiver extends BroadcastRecei
return;
} else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
&& !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
if (null == mService) {
Log.e(TAG, "Called with intent " + action + " but we don't know the service: this "
+ "should never happen");
return;
}
// When the dictionary package is removed, we need to reread dictionary (to use the
// next-priority one, or stop using a dictionary at all if this was the only one,
// since this is the user request).
@ -82,7 +113,28 @@ public final class DictionaryPackInstallBroadcastReceiver extends BroadcastRecei
// read dictionary from?
mService.resetSuggestMainDict();
} else if (action.equals(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)) {
if (null == mService) {
Log.e(TAG, "Called with intent " + action + " but we don't know the service: this "
+ "should never happen");
return;
}
mService.resetSuggestMainDict();
} else if (action.equals(DictionaryPackConstants.UNKNOWN_DICTIONARY_PROVIDER_CLIENT)) {
if (null != mService) {
// Careful! This is returning if the service is NOT null. This is because we
// should come here instantiated by the framework in reaction to a broadcast of
// the above action, so we should gave gone through the no-args constructor.
Log.e(TAG, "Called with intent " + action + " but we have a reference to the "
+ "service: this should never happen");
return;
}
// The dictionary provider does not know about some client. We check that it's really
// us that it needs to know about, and if it's the case, we register with the provider.
final String wantedClientId =
intent.getStringExtra(DictionaryPackConstants.DICTIONARY_PROVIDER_CLIENT_EXTRA);
final String myClientId = context.getString(R.string.dictionary_pack_client_id);
if (!wantedClientId.equals(myClientId)) return; // Not for us
BinaryDictionaryFileDumper.initializeClientRecordHelper(context, myClientId);
}
}
}