mobile/android/base/sync/CommandProcessor.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 package org.mozilla.gecko.sync;
     7 import java.util.ArrayList;
     8 import java.util.List;
     9 import java.util.Map;
    10 import java.util.concurrent.ConcurrentHashMap;
    11 import java.util.concurrent.atomic.AtomicInteger;
    13 import org.json.simple.JSONArray;
    14 import org.json.simple.JSONObject;
    15 import org.mozilla.gecko.BrowserLocaleManager;
    16 import org.mozilla.gecko.R;
    17 import org.mozilla.gecko.background.common.log.Logger;
    18 import org.mozilla.gecko.sync.repositories.NullCursorException;
    19 import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
    20 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
    22 import android.app.Notification;
    23 import android.app.NotificationManager;
    24 import android.app.PendingIntent;
    25 import android.content.Context;
    26 import android.content.Intent;
    27 import android.net.Uri;
    29 /**
    30  * Process commands received from Sync clients.
    31  * <p>
    32  * We need a command processor at two different times:
    33  * <ol>
    34  * <li>We execute commands during the "clients" engine stage of a Sync. Each
    35  * command takes a <code>GlobalSession</code> instance as a parameter.</li>
    36  * <li>We queue commands to be executed or propagated to other Sync clients
    37  * during an activity completely unrelated to a sync (such as
    38  * <code>SendTabActivity</code>.)</li>
    39  * </ol>
    40  * To provide a processor for both these time frames, we maintain a static
    41  * long-lived singleton.
    42  */
    43 public class CommandProcessor {
    44   private static final String LOG_TAG = "Command";
    45   private static AtomicInteger currentId = new AtomicInteger();
    46   protected ConcurrentHashMap<String, CommandRunner> commands = new ConcurrentHashMap<String, CommandRunner>();
    48   private final static CommandProcessor processor = new CommandProcessor();
    50   /**
    51    * Get the global singleton command processor.
    52    *
    53    * @return the singleton processor.
    54    */
    55   public static CommandProcessor getProcessor() {
    56     return processor;
    57   }
    59   public static class Command {
    60     public final String commandType;
    61     public final JSONArray args;
    62     private List<String> argsList;
    64     public Command(String commandType, JSONArray args) {
    65       this.commandType = commandType;
    66       this.args = args;
    67     }
    69     /**
    70      * Get list of arguments as strings.  Individual arguments may be null.
    71      *
    72      * @return list of strings.
    73      */
    74     public synchronized List<String> getArgsList() {
    75       if (argsList == null) {
    76         ArrayList<String> argsList = new ArrayList<String>(args.size());
    78         for (int i = 0; i < args.size(); i++) {
    79           final Object arg = args.get(i);
    80           if (arg == null) {
    81             argsList.add(null);
    82             continue;
    83           }
    84           argsList.add(arg.toString());
    85         }
    86         this.argsList = argsList;
    87       }
    88       return this.argsList;
    89     }
    91     @SuppressWarnings("unchecked")
    92     public JSONObject asJSONObject() {
    93       JSONObject out = new JSONObject();
    94       out.put("command", this.commandType);
    95       out.put("args", this.args);
    96       return out;
    97     }
    98   }
   100   /**
   101    * Register a command.
   102    * <p>
   103    * Any existing registration is overwritten.
   104    *
   105    * @param commandType
   106    *          the name of the command, i.e., "displayURI".
   107    * @param command
   108    *          the <code>CommandRunner</code> instance that should handle the
   109    *          command.
   110    */
   111   public void registerCommand(String commandType, CommandRunner command) {
   112     commands.put(commandType, command);
   113   }
   115   /**
   116    * Process a command in the context of the given global session.
   117    *
   118    * @param session
   119    *          the <code>GlobalSession</code> instance currently executing.
   120    * @param unparsedCommand
   121    *          command as a <code>ExtendedJSONObject</code> instance.
   122    */
   123   public void processCommand(final GlobalSession session, ExtendedJSONObject unparsedCommand) {
   124     Command command = parseCommand(unparsedCommand);
   125     if (command == null) {
   126       Logger.debug(LOG_TAG, "Invalid command: " + unparsedCommand + " will not be processed.");
   127       return;
   128     }
   130     CommandRunner executableCommand = commands.get(command.commandType);
   131     if (executableCommand == null) {
   132       Logger.debug(LOG_TAG, "Command \"" + command.commandType + "\" not registered and will not be processed.");
   133       return;
   134     }
   136     executableCommand.executeCommand(session, command.getArgsList());
   137   }
   139   /**
   140    * Parse a JSON command into a ParsedCommand object for easier handling.
   141    *
   142    * @param unparsedCommand - command as ExtendedJSONObject
   143    * @return - null if command is invalid, else return ParsedCommand with
   144    *           no null attributes.
   145    */
   146   protected static Command parseCommand(ExtendedJSONObject unparsedCommand) {
   147     String type = (String) unparsedCommand.get("command");
   148     if (type == null) {
   149       return null;
   150     }
   152     try {
   153       JSONArray unparsedArgs = unparsedCommand.getArray("args");
   154       if (unparsedArgs == null) {
   155         return null;
   156       }
   158       return new Command(type, unparsedArgs);
   159     } catch (NonArrayJSONException e) {
   160       Logger.debug(LOG_TAG, "Unable to parse args array. Invalid command");
   161       return null;
   162     }
   163   }
   165   @SuppressWarnings("unchecked")
   166   public void sendURIToClientForDisplay(String uri, String clientID, String title, String sender, Context context) {
   167     Logger.info(LOG_TAG, "Sending URI to client " + clientID + ".");
   168     if (Logger.LOG_PERSONAL_INFORMATION) {
   169       Logger.pii(LOG_TAG, "URI is " + uri + "; title is '" + title + "'.");
   170     }
   172     final JSONArray args = new JSONArray();
   173     args.add(uri);
   174     args.add(sender);
   175     args.add(title);
   177     final Command displayURICommand = new Command("displayURI", args);
   178     this.sendCommand(clientID, displayURICommand, context);
   179   }
   181   /**
   182    * Validates and sends a command to a client or all clients.
   183    *
   184    * Calling this does not actually sync the command data to the server. If the
   185    * client already has the command/args pair, it won't receive a duplicate
   186    * command.
   187    *
   188    * @param clientID
   189    *        Client ID to send command to. If null, send to all remote
   190    *        clients.
   191    * @param command
   192    *        Command to invoke on remote clients
   193    */
   194   public void sendCommand(String clientID, Command command, Context context) {
   195     Logger.debug(LOG_TAG, "In sendCommand.");
   197     CommandRunner commandData = commands.get(command.commandType);
   199     // Don't send commands that we don't know about.
   200     if (commandData == null) {
   201       Logger.error(LOG_TAG, "Unknown command to send: " + command);
   202       return;
   203     }
   205     // Don't send a command with the wrong number of arguments.
   206     if (!commandData.argumentsAreValid(command.getArgsList())) {
   207       Logger.error(LOG_TAG, "Expected " + commandData.argCount + " args for '" +
   208                    command + "', but got " + command.args);
   209       return;
   210     }
   212     if (clientID != null) {
   213       this.sendCommandToClient(clientID, command, context);
   214       return;
   215     }
   217     ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(context);
   218     try {
   219       Map<String, ClientRecord> clientMap = db.fetchAllClients();
   220       for (ClientRecord client : clientMap.values()) {
   221         this.sendCommandToClient(client.guid, command, context);
   222       }
   223     } catch (NullCursorException e) {
   224       Logger.error(LOG_TAG, "NullCursorException when fetching all GUIDs");
   225     } finally {
   226       db.close();
   227     }
   228   }
   230   protected void sendCommandToClient(String clientID, Command command, Context context) {
   231     Logger.info(LOG_TAG, "Sending " + command.commandType + " to " + clientID);
   233     ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(context);
   234     try {
   235       db.store(clientID, command);
   236     } catch (NullCursorException e) {
   237       Logger.error(LOG_TAG, "NullCursorException: Unable to send command.");
   238     } finally {
   239       db.close();
   240     }
   241   }
   243   private static volatile boolean didUpdateLocale = false;
   245   @SuppressWarnings("deprecation")
   246   public static void displayURI(final List<String> args, final Context context) {
   247     // We trust the client sender that these exist.
   248     final String uri = args.get(0);
   249     final String clientId = args.get(1);
   251     Logger.pii(LOG_TAG, "Received a URI for display: " + uri + " from " + clientId);
   253     String title = null;
   254     if (args.size() == 3) {
   255       title = args.get(2);
   256     }
   258     // We don't care too much about races, but let's try to avoid
   259     // unnecessary work.
   260     if (!didUpdateLocale) {
   261       BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(context);
   262       didUpdateLocale = true;
   263     }
   265     final String ns = Context.NOTIFICATION_SERVICE;
   266     final NotificationManager notificationManager = (NotificationManager) context.getSystemService(ns);
   268     // Create a Notification.
   269     final int icon = R.drawable.icon;
   270     String notificationTitle = context.getString(R.string.sync_new_tab);
   271     if (title != null) {
   272       notificationTitle = notificationTitle.concat(": " + title);
   273     }
   275     final long when = System.currentTimeMillis();
   276     Notification notification = new Notification(icon, notificationTitle, when);
   277     notification.flags = Notification.FLAG_AUTO_CANCEL;
   279     // Set pending intent associated with the notification.
   280     Intent notificationIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
   281     PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
   282     notification.setLatestEventInfo(context, notificationTitle, uri, contentIntent);
   284     // Send notification.
   285     notificationManager.notify(currentId.getAndIncrement(), notification);
   286   }
   287 }

mercurial