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

Upon invoking the settings of the dictionary pack with an unknown
client, we now launch an intent to ask the client to make itself known.
This change also includes the code that receives this intent and
acts upon it.

Bug: 8492879
Change-Id: I2c6496dea845646961ecafcf64e282cb93ee91dc
main
Jean Chalard 2013-03-28 18:59:19 +09:00
parent c1dec87a76
commit 76d5f512f9
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);
}
}
}