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.Shared.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