Home | History | Annotate | Download | only in queries
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.documentsui.queries;
     17 
     18 import static com.android.documentsui.base.SharedMinimal.DEBUG;
     19 
     20 import android.content.Context;
     21 import android.support.annotation.VisibleForTesting;
     22 import android.text.TextUtils;
     23 import android.util.Log;
     24 
     25 import com.android.documentsui.DocumentsApplication;
     26 import com.android.documentsui.R;
     27 import com.android.documentsui.base.DebugFlags;
     28 import com.android.documentsui.base.EventHandler;
     29 import com.android.documentsui.base.Features;
     30 
     31 import java.util.ArrayList;
     32 import java.util.List;
     33 
     34 public final class CommandInterceptor implements EventHandler<String> {
     35 
     36     @VisibleForTesting
     37     static final String COMMAND_PREFIX = ":";
     38 
     39     private static final String TAG = "CommandInterceptor";
     40 
     41     private final List<EventHandler<String[]>> mCommands = new ArrayList<>();
     42 
     43     private Features mFeatures;
     44 
     45     public CommandInterceptor(Features features) {
     46         mFeatures = features;
     47 
     48         mCommands.add(this::quickViewer);
     49         mCommands.add(this::gestureScale);
     50         mCommands.add(this::jobProgressDialog);
     51         mCommands.add(this::archiveCreation);
     52         mCommands.add(this::docInspector);
     53         mCommands.add(this::docDetails);
     54         mCommands.add(this::forcePaging);
     55     }
     56 
     57     public void add(EventHandler<String[]> handler) {
     58         mCommands.add(handler);
     59     }
     60 
     61     @Override
     62     public boolean accept(String query) {
     63         if (!mFeatures.isDebugSupportEnabled()) {
     64             return false;
     65         }
     66 
     67         if (!mFeatures.isCommandInterceptorEnabled()) {
     68             if (DEBUG) Log.v(TAG, "Skipping input, command interceptor disabled.");
     69             return false;
     70         }
     71 
     72         if (query.length() > COMMAND_PREFIX.length() && query.startsWith(COMMAND_PREFIX)) {
     73             String[] tokens = query.substring(COMMAND_PREFIX.length()).split("\\s+");
     74             for (EventHandler<String[]> command : mCommands) {
     75                 if (command.accept(tokens)) {
     76                     return true;
     77                 }
     78             }
     79             Log.d(TAG, "Unrecognized debug command: " + query);
     80         }
     81         return false;
     82     }
     83 
     84     private boolean quickViewer(String[] tokens) {
     85         if ("qv".equals(tokens[0])) {
     86             if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
     87                 DebugFlags.setQuickViewer(tokens[1]);
     88                 Log.i(TAG, "Set quick viewer to: " + tokens[1]);
     89                 return true;
     90             } else {
     91                 Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
     92             }
     93         } else if ("deqv".equals(tokens[0])) {
     94             Log.i(TAG, "Unset quick viewer");
     95             DebugFlags.setQuickViewer(null);
     96             return true;
     97         }
     98         return false;
     99     }
    100 
    101     private boolean gestureScale(String[] tokens) {
    102         if ("gs".equals(tokens[0])) {
    103             if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
    104                 boolean enabled = asBool(tokens[1]);
    105                 mFeatures.forceFeature(R.bool.feature_gesture_scale, enabled);
    106                 Log.i(TAG, "Set gesture scale enabled to: " + enabled);
    107                 return true;
    108             }
    109             Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
    110         }
    111         return false;
    112     }
    113 
    114     private boolean jobProgressDialog(String[] tokens) {
    115         if ("jpd".equals(tokens[0])) {
    116             if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
    117                 boolean enabled = asBool(tokens[1]);
    118                 mFeatures.forceFeature(R.bool.feature_job_progress_dialog, enabled);
    119                 Log.i(TAG, "Set job progress dialog enabled to: " + enabled);
    120                 return true;
    121             }
    122             Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
    123         }
    124         return false;
    125     }
    126 
    127     private boolean archiveCreation(String[] tokens) {
    128         if ("zip".equals(tokens[0])) {
    129             if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
    130                 boolean enabled = asBool(tokens[1]);
    131                 mFeatures.forceFeature(R.bool.feature_archive_creation, enabled);
    132                 Log.i(TAG, "Set gesture scale enabled to: " + enabled);
    133                 return true;
    134             }
    135             Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
    136         }
    137         return false;
    138     }
    139 
    140     private boolean docInspector(String[] tokens) {
    141         if ("inspect".equals(tokens[0])) {
    142             if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
    143                 boolean enabled = asBool(tokens[1]);
    144                 mFeatures.forceFeature(R.bool.feature_inspector, enabled);
    145                 Log.i(TAG, "Set doc inspector enabled to: " + enabled);
    146                 return true;
    147             }
    148             Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
    149         }
    150         return false;
    151     }
    152 
    153     private boolean docDetails(String[] tokens) {
    154         if ("docinfo".equals(tokens[0])) {
    155             if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
    156                 boolean enabled = asBool(tokens[1]);
    157                 DebugFlags.setDocumentDetailsEnabled(enabled);
    158                 Log.i(TAG, "Set doc details enabled to: " + enabled);
    159                 return true;
    160             }
    161             Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
    162         }
    163         return false;
    164     }
    165 
    166     private boolean forcePaging(String[] tokens) {
    167         if ("page".equals(tokens[0])) {
    168             if (tokens.length >= 2) {
    169                 try {
    170                     int offset = Integer.parseInt(tokens[1]);
    171                     int limit = (tokens.length == 3) ? Integer.parseInt(tokens[2]) : -1;
    172                     DebugFlags.setForcedPaging(offset, limit);
    173                     Log.i(TAG, "Set forced paging to offset: " + offset + ", limit: " + limit);
    174                     return true;
    175                 } catch (NumberFormatException e) {
    176                     Log.w(TAG, "Command input does not contain valid numbers: "
    177                             + TextUtils.join(" ", tokens));
    178                     return false;
    179                 }
    180             } else {
    181                 Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
    182             }
    183         } else if ("deqv".equals(tokens[0])) {
    184             Log.i(TAG, "Unset quick viewer");
    185             DebugFlags.setQuickViewer(null);
    186             return true;
    187         }
    188         return false;
    189     }
    190 
    191     private final boolean asBool(String val) {
    192         if (val == null || val.equals("0")) {
    193             return false;
    194         }
    195         if (val.equals("1")) {
    196             return true;
    197         }
    198         return Boolean.valueOf(val);
    199     }
    200 
    201     public static final class DumpRootsCacheHandler implements EventHandler<String[]> {
    202         private final Context mContext;
    203 
    204         public DumpRootsCacheHandler(Context context) {
    205             mContext = context;
    206         }
    207 
    208         @Override
    209         public boolean accept(String[] tokens) {
    210             if ("dumpCache".equals(tokens[0])) {
    211                 DocumentsApplication.getProvidersCache(mContext).logCache();
    212                 return true;
    213             }
    214             return false;
    215         }
    216     }
    217 
    218     /**
    219      * Wraps {@link CommandInterceptor} in a tiny decorator that adds support for
    220      * enabling CommandInterceptor feature based on some magic query input.
    221      *
    222      * <p>It's like super meta, maaaannn.
    223      */
    224     public static final EventHandler<String> createDebugModeFlipper(
    225             Features features,
    226             Runnable debugFlipper,
    227             CommandInterceptor interceptor) {
    228 
    229         if (!features.isDebugSupportEnabled()) {
    230             return interceptor;
    231         }
    232 
    233         String magicString1 = COMMAND_PREFIX + "wwssadadba";
    234         String magicString2 = "up up down down left right left right b a";
    235 
    236         return new EventHandler<String>() {
    237             @Override
    238             public boolean accept(String query) {
    239                 assert(features.isDebugSupportEnabled());
    240 
    241                 if (magicString1.equals(query) || magicString2.equals(query)) {
    242                     debugFlipper.run();
    243                 }
    244                 return interceptor.accept(query);
    245             }
    246         };
    247     }
    248 }
    249