michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.sync; michael@0: michael@0: import java.util.ArrayList; michael@0: import java.util.List; michael@0: import java.util.Map; michael@0: import java.util.concurrent.ConcurrentHashMap; michael@0: import java.util.concurrent.atomic.AtomicInteger; michael@0: michael@0: import org.json.simple.JSONArray; michael@0: import org.json.simple.JSONObject; michael@0: import org.mozilla.gecko.BrowserLocaleManager; michael@0: import org.mozilla.gecko.R; michael@0: import org.mozilla.gecko.background.common.log.Logger; michael@0: import org.mozilla.gecko.sync.repositories.NullCursorException; michael@0: import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor; michael@0: import org.mozilla.gecko.sync.repositories.domain.ClientRecord; michael@0: michael@0: import android.app.Notification; michael@0: import android.app.NotificationManager; michael@0: import android.app.PendingIntent; michael@0: import android.content.Context; michael@0: import android.content.Intent; michael@0: import android.net.Uri; michael@0: michael@0: /** michael@0: * Process commands received from Sync clients. michael@0: *
michael@0: * We need a command processor at two different times: michael@0: *
GlobalSession
instance as a parameter.SendTabActivity
.)
michael@0: * Any existing registration is overwritten.
michael@0: *
michael@0: * @param commandType
michael@0: * the name of the command, i.e., "displayURI".
michael@0: * @param command
michael@0: * the CommandRunner
instance that should handle the
michael@0: * command.
michael@0: */
michael@0: public void registerCommand(String commandType, CommandRunner command) {
michael@0: commands.put(commandType, command);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Process a command in the context of the given global session.
michael@0: *
michael@0: * @param session
michael@0: * the GlobalSession
instance currently executing.
michael@0: * @param unparsedCommand
michael@0: * command as a ExtendedJSONObject
instance.
michael@0: */
michael@0: public void processCommand(final GlobalSession session, ExtendedJSONObject unparsedCommand) {
michael@0: Command command = parseCommand(unparsedCommand);
michael@0: if (command == null) {
michael@0: Logger.debug(LOG_TAG, "Invalid command: " + unparsedCommand + " will not be processed.");
michael@0: return;
michael@0: }
michael@0:
michael@0: CommandRunner executableCommand = commands.get(command.commandType);
michael@0: if (executableCommand == null) {
michael@0: Logger.debug(LOG_TAG, "Command \"" + command.commandType + "\" not registered and will not be processed.");
michael@0: return;
michael@0: }
michael@0:
michael@0: executableCommand.executeCommand(session, command.getArgsList());
michael@0: }
michael@0:
michael@0: /**
michael@0: * Parse a JSON command into a ParsedCommand object for easier handling.
michael@0: *
michael@0: * @param unparsedCommand - command as ExtendedJSONObject
michael@0: * @return - null if command is invalid, else return ParsedCommand with
michael@0: * no null attributes.
michael@0: */
michael@0: protected static Command parseCommand(ExtendedJSONObject unparsedCommand) {
michael@0: String type = (String) unparsedCommand.get("command");
michael@0: if (type == null) {
michael@0: return null;
michael@0: }
michael@0:
michael@0: try {
michael@0: JSONArray unparsedArgs = unparsedCommand.getArray("args");
michael@0: if (unparsedArgs == null) {
michael@0: return null;
michael@0: }
michael@0:
michael@0: return new Command(type, unparsedArgs);
michael@0: } catch (NonArrayJSONException e) {
michael@0: Logger.debug(LOG_TAG, "Unable to parse args array. Invalid command");
michael@0: return null;
michael@0: }
michael@0: }
michael@0:
michael@0: @SuppressWarnings("unchecked")
michael@0: public void sendURIToClientForDisplay(String uri, String clientID, String title, String sender, Context context) {
michael@0: Logger.info(LOG_TAG, "Sending URI to client " + clientID + ".");
michael@0: if (Logger.LOG_PERSONAL_INFORMATION) {
michael@0: Logger.pii(LOG_TAG, "URI is " + uri + "; title is '" + title + "'.");
michael@0: }
michael@0:
michael@0: final JSONArray args = new JSONArray();
michael@0: args.add(uri);
michael@0: args.add(sender);
michael@0: args.add(title);
michael@0:
michael@0: final Command displayURICommand = new Command("displayURI", args);
michael@0: this.sendCommand(clientID, displayURICommand, context);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Validates and sends a command to a client or all clients.
michael@0: *
michael@0: * Calling this does not actually sync the command data to the server. If the
michael@0: * client already has the command/args pair, it won't receive a duplicate
michael@0: * command.
michael@0: *
michael@0: * @param clientID
michael@0: * Client ID to send command to. If null, send to all remote
michael@0: * clients.
michael@0: * @param command
michael@0: * Command to invoke on remote clients
michael@0: */
michael@0: public void sendCommand(String clientID, Command command, Context context) {
michael@0: Logger.debug(LOG_TAG, "In sendCommand.");
michael@0:
michael@0: CommandRunner commandData = commands.get(command.commandType);
michael@0:
michael@0: // Don't send commands that we don't know about.
michael@0: if (commandData == null) {
michael@0: Logger.error(LOG_TAG, "Unknown command to send: " + command);
michael@0: return;
michael@0: }
michael@0:
michael@0: // Don't send a command with the wrong number of arguments.
michael@0: if (!commandData.argumentsAreValid(command.getArgsList())) {
michael@0: Logger.error(LOG_TAG, "Expected " + commandData.argCount + " args for '" +
michael@0: command + "', but got " + command.args);
michael@0: return;
michael@0: }
michael@0:
michael@0: if (clientID != null) {
michael@0: this.sendCommandToClient(clientID, command, context);
michael@0: return;
michael@0: }
michael@0:
michael@0: ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(context);
michael@0: try {
michael@0: Map