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 */ 16package com.android.documentsui.queries; 17 18import static com.android.documentsui.base.Shared.DEBUG; 19 20import android.content.Context; 21import android.support.annotation.VisibleForTesting; 22import android.text.TextUtils; 23import android.util.Log; 24 25import com.android.documentsui.DocumentsApplication; 26import com.android.documentsui.R; 27import com.android.documentsui.base.DebugFlags; 28import com.android.documentsui.base.EventHandler; 29import com.android.documentsui.base.Features; 30 31import java.util.ArrayList; 32import java.util.List; 33 34public 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::archiveCreation); 51 mCommands.add(this::docDetails); 52 mCommands.add(this::forcePaging); 53 } 54 55 public void add(EventHandler<String[]> handler) { 56 mCommands.add(handler); 57 } 58 59 @Override 60 public boolean accept(String query) { 61 if (!mFeatures.isCommandInterceptorEnabled()) { 62 if (DEBUG) Log.v(TAG, "Skipping input, command interceptor disabled."); 63 return false; 64 } 65 66 if (query.length() > COMMAND_PREFIX.length() && query.startsWith(COMMAND_PREFIX)) { 67 String[] tokens = query.substring(COMMAND_PREFIX.length()).split("\\s+"); 68 for (EventHandler<String[]> command : mCommands) { 69 if (command.accept(tokens)) { 70 return true; 71 } 72 } 73 Log.d(TAG, "Unrecognized debug command: " + query); 74 } 75 return false; 76 } 77 78 private boolean quickViewer(String[] tokens) { 79 if ("qv".equals(tokens[0])) { 80 if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) { 81 DebugFlags.setQuickViewer(tokens[1]); 82 Log.i(TAG, "Set quick viewer to: " + tokens[1]); 83 return true; 84 } else { 85 Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens)); 86 } 87 } else if ("deqv".equals(tokens[0])) { 88 Log.i(TAG, "Unset quick viewer"); 89 DebugFlags.setQuickViewer(null); 90 return true; 91 } 92 return false; 93 } 94 95 private boolean gestureScale(String[] tokens) { 96 if ("gs".equals(tokens[0])) { 97 if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) { 98 boolean enabled = asBool(tokens[1]); 99 mFeatures.forceFeature(R.bool.feature_gesture_scale, enabled); 100 Log.i(TAG, "Set gesture scale enabled to: " + enabled); 101 return true; 102 } 103 Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens)); 104 } 105 return false; 106 } 107 108 private boolean archiveCreation(String[] tokens) { 109 if ("zip".equals(tokens[0])) { 110 if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) { 111 boolean enabled = asBool(tokens[1]); 112 mFeatures.forceFeature(R.bool.feature_archive_creation, enabled); 113 Log.i(TAG, "Set gesture scale enabled to: " + enabled); 114 return true; 115 } 116 Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens)); 117 } 118 return false; 119 } 120 121 private boolean docDetails(String[] tokens) { 122 if ("docinfo".equals(tokens[0])) { 123 if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) { 124 boolean enabled = asBool(tokens[1]); 125 DebugFlags.setDocumentDetailsEnabled(enabled); 126 Log.i(TAG, "Set gesture scale enabled to: " + enabled); 127 return true; 128 } 129 Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens)); 130 } 131 return false; 132 } 133 134 private boolean forcePaging(String[] tokens) { 135 if ("page".equals(tokens[0])) { 136 if (tokens.length >= 2) { 137 try { 138 int offset = Integer.parseInt(tokens[1]); 139 int limit = (tokens.length == 3) ? Integer.parseInt(tokens[2]) : -1; 140 DebugFlags.setForcedPaging(offset, limit); 141 Log.i(TAG, "Set forced paging to offset: " + offset + ", limit: " + limit); 142 return true; 143 } catch (NumberFormatException e) { 144 Log.w(TAG, "Command input does not contain valid numbers: " 145 + TextUtils.join(" ", tokens)); 146 return false; 147 } 148 } else { 149 Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens)); 150 } 151 } else if ("deqv".equals(tokens[0])) { 152 Log.i(TAG, "Unset quick viewer"); 153 DebugFlags.setQuickViewer(null); 154 return true; 155 } 156 return false; 157 } 158 159 private final boolean asBool(String val) { 160 if (val == null || val.equals("0")) { 161 return false; 162 } 163 if (val.equals("1")) { 164 return true; 165 } 166 return Boolean.valueOf(val); 167 } 168 169 public static final class DumpRootsCacheHandler implements EventHandler<String[]> { 170 private final Context mContext; 171 172 public DumpRootsCacheHandler(Context context) { 173 mContext = context; 174 } 175 176 @Override 177 public boolean accept(String[] tokens) { 178 if ("dumpCache".equals(tokens[0])) { 179 DocumentsApplication.getProvidersCache(mContext).logCache(); 180 return true; 181 } 182 return false; 183 } 184 } 185} 186