1 /* 2 * Copyright (C) 2022 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 17 package com.android.providers.media; 18 19 import static android.os.Process.THREAD_PRIORITY_FOREGROUND; 20 21 import static com.android.providers.media.photopicker.util.CloudProviderUtils.getAllAvailableCloudProviders; 22 import static com.android.providers.media.photopicker.util.CloudProviderUtils.getAvailableCloudProviders; 23 24 import android.content.Context; 25 import android.os.Handler; 26 import android.os.HandlerThread; 27 import android.os.ParcelFileDescriptor; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 32 import com.android.modules.utils.BasicShellCommandHandler; 33 import com.android.modules.utils.HandlerExecutor; 34 import com.android.providers.media.photopicker.PickerSyncController; 35 import com.android.providers.media.photopicker.data.CloudProviderInfo; 36 import com.android.providers.media.photopicker.data.PickerDatabaseHelper; 37 import com.android.providers.media.photopicker.util.exceptions.UnableToAcquireLockException; 38 39 import java.io.OutputStream; 40 import java.io.PrintWriter; 41 import java.util.List; 42 import java.util.concurrent.Executor; 43 44 class MediaProviderShellCommand extends BasicShellCommandHandler { 45 private final @NonNull Context mAppContext; 46 private final @NonNull ConfigStore mConfigStore; 47 private final @NonNull PickerSyncController mPickerSyncController; 48 private final @NonNull OutputStream mOut; 49 MediaProviderShellCommand( @onNull Context context, @NonNull ConfigStore configStore, @NonNull PickerSyncController pickerSyncController, @NonNull ParcelFileDescriptor out)50 MediaProviderShellCommand( 51 @NonNull Context context, 52 @NonNull ConfigStore configStore, 53 @NonNull PickerSyncController pickerSyncController, 54 @NonNull ParcelFileDescriptor out) { 55 mAppContext = context.getApplicationContext(); 56 mPickerSyncController = pickerSyncController; 57 mConfigStore = configStore; 58 mOut = new ParcelFileDescriptor.AutoCloseOutputStream(out); 59 } 60 61 @Override onCommand(String cmd)62 public int onCommand(String cmd) { 63 try (PrintWriter pw = getOutPrintWriter()) { 64 if (cmd == null || cmd.isBlank()) { 65 cmd = "help"; 66 } 67 switch (cmd) { 68 case "version": 69 return runVersion(pw); 70 case "cloud-provider": 71 return runCloudProvider(pw); 72 default: 73 return handleDefaultCommands(cmd); 74 } 75 } 76 } 77 runVersion(@onNull PrintWriter pw)78 private int runVersion(@NonNull PrintWriter pw) { 79 pw.print('\'' + DatabaseHelper.INTERNAL_DATABASE_NAME + "' version: "); 80 pw.println(DatabaseHelper.VERSION_LATEST); 81 82 pw.print('\'' + DatabaseHelper.EXTERNAL_DATABASE_NAME + "' version: "); 83 pw.println(DatabaseHelper.VERSION_LATEST); 84 85 pw.print('\'' + PickerDatabaseHelper.PICKER_DATABASE_NAME + "' version: "); 86 pw.println(PickerDatabaseHelper.VERSION_LATEST); 87 88 return 0; 89 } 90 runCloudProvider(@onNull PrintWriter pw)91 private int runCloudProvider(@NonNull PrintWriter pw) { 92 final String subcommand = getNextArgRequired(); 93 switch (subcommand) { 94 case "list": 95 return runCloudProviderList(pw); 96 case "info": 97 return runCloudProviderInfo(pw); 98 case "set": 99 return runCloudProviderSet(pw); 100 case "unset": 101 return runCloudProviderUnset(pw); 102 case "sync-library": 103 return runCloudProviderSyncLibrary(pw); 104 case "reset-library": 105 return runCloudProviderResetLibrary(pw); 106 default: 107 pw.println("Error: unknown cloud-provider command '" + subcommand + "'"); 108 return 1; 109 } 110 } 111 runCloudProviderList(@onNull PrintWriter pw)112 private int runCloudProviderList(@NonNull PrintWriter pw) { 113 final String option = getNextOption(); 114 if ("--allowlist".equals(option)) { 115 final List<String> allowlist = mConfigStore.getAllowedCloudProviderPackages(); 116 if (allowlist.isEmpty()) { 117 pw.println("Allowlist is empty."); 118 } else { 119 for (var providerAuthority : allowlist) { 120 pw.println(providerAuthority); 121 } 122 } 123 } else { 124 final List<CloudProviderInfo> cloudProviders; 125 126 if ("--all".equals(option)) { 127 cloudProviders = getAllAvailableCloudProviders(mAppContext, mConfigStore); 128 } else if (option == null) { 129 cloudProviders = getAvailableCloudProviders(mAppContext, mConfigStore); 130 } else { 131 pw.println("Error: unknown cloud-provider list option '" + option + "'"); 132 return 1; 133 } 134 135 if (cloudProviders.isEmpty()) { 136 pw.println("No available CloudMediaProviders."); 137 } else { 138 for (var providerInfo : cloudProviders) { 139 pw.println(providerInfo.toShortString()); 140 } 141 } 142 } 143 return 0; 144 } 145 runCloudProviderInfo(@onNull PrintWriter pw)146 private int runCloudProviderInfo(@NonNull PrintWriter pw) { 147 pw.println("Current CloudMediaProvider:"); 148 pw.println(mPickerSyncController.getCurrentCloudProviderInfo().toShortString()); 149 return 0; 150 } 151 runCloudProviderSet(@onNull PrintWriter pw)152 private int runCloudProviderSet(@NonNull PrintWriter pw) { 153 final String authority = getNextArg(); 154 if (authority == null) { 155 pw.println("Error: authority not provided"); 156 pw.println("(usage: `media_provider cloud-provider set <authority>`)"); 157 return 1; 158 } 159 160 pw.println("Setting current CloudMediaProvider authority to '" + authority + "'..."); 161 final boolean success = mPickerSyncController.forceSetCloudProvider(authority); 162 163 pw.println(success ? "Succeed." : "Failed."); 164 return success ? 0 : 1; 165 } 166 runCloudProviderUnset(@onNull PrintWriter pw)167 private int runCloudProviderUnset(@NonNull PrintWriter pw) { 168 pw.println("Unsetting current CloudMediaProvider (disabling CMP integration)..."); 169 final boolean success = mPickerSyncController.forceSetCloudProvider(null); 170 171 pw.println(success ? "Succeed." : "Failed."); 172 return success ? 0 : 1; 173 } 174 runCloudProviderSyncLibrary(@onNull PrintWriter pw)175 private int runCloudProviderSyncLibrary(@NonNull PrintWriter pw) { 176 pw.println("Syncing PhotoPicker's library (CMP and local)..."); 177 178 // TODO(b/242550131): add PickerSyncController's API to make it possible to sync from only 179 // one provider at a time (i.e. either CMP or local) 180 mPickerSyncController.syncAllMedia(); 181 182 pw.println("Done."); 183 return 0; 184 } 185 runCloudProviderResetLibrary(@onNull PrintWriter pw)186 private int runCloudProviderResetLibrary(@NonNull PrintWriter pw) { 187 pw.println("Resetting PhotoPicker's library (CMP and local)..."); 188 189 // TODO(b/242550131): add PickerSyncController's API to make it possible to reset just one 190 // provider's library at a time (i.e. either CMP or local). 191 try { 192 mPickerSyncController.resetAllMedia(); 193 } catch (UnableToAcquireLockException e) { 194 pw.print("Could not reset all media" + e.getMessage()); 195 return 1; 196 } 197 198 pw.println("Done."); 199 return 0; 200 } 201 202 @Override onHelp()203 public void onHelp() { 204 final PrintWriter pw = getOutPrintWriter(); 205 pw.println("MediaProvider (media_provider) commands:"); 206 pw.println(" help"); 207 pw.println(" Print this help text."); 208 pw.println(); 209 pw.println(" version"); 210 pw.println(" Print databases (internal/external/picker) versions."); 211 pw.println(); 212 pw.println(" cloud-provider [list | info | set | unset] [...]"); 213 pw.println(" Configure and audit CloudMediaProvider-s (CMPs)."); 214 pw.println(); 215 pw.println(" list [--all | --allowlist]"); 216 pw.println(" List installed and allowlisted CMPs."); 217 pw.println(" --all: ignore allowlist, list all installed CMPs."); 218 pw.println(" --allowlisted: print allowlist of CMP authorities."); 219 pw.println(); 220 pw.println(" info"); 221 pw.println(" Print current CloudMediaProvider."); 222 pw.println(); 223 pw.println(" set <AUTHORITY>"); 224 pw.println(" Set current CloudMediaProvider."); 225 pw.println(); 226 pw.println(" unset"); 227 pw.println(" Unset CloudMediaProvider (disables CMP integration)."); 228 pw.println(); 229 pw.println(" sync-library"); 230 pw.println(" Sync media from the current CloudMediaProvider and local provider."); 231 pw.println(); 232 pw.println(" reset-library"); 233 pw.println(" Reset media previously synced from the CloudMediaProvider and"); 234 pw.println(" the local provider."); 235 pw.println(); 236 } 237 exec(@ullable String[] args)238 public void exec(@Nullable String[] args) { 239 getExecutor().execute(() -> exec( 240 /* Binder target */ null, 241 /* FileDescriptor in */ null, 242 /* FileDescriptor out */ null, 243 /* FileDescriptor err */ null, 244 args)); 245 } 246 247 248 @Override getRawOutputStream()249 public OutputStream getRawOutputStream() { 250 return mOut; 251 } 252 253 @Override getRawErrorStream()254 public OutputStream getRawErrorStream() { 255 return mOut; 256 } 257 258 @Nullable 259 private static Executor sExecutor; 260 261 @NonNull getExecutor()262 private static synchronized Executor getExecutor() { 263 if (sExecutor == null) { 264 final HandlerThread thread = new HandlerThread("cli", THREAD_PRIORITY_FOREGROUND); 265 thread.start(); 266 final Handler handler = new Handler(thread.getLooper()); 267 sExecutor = new HandlerExecutor(handler); 268 } 269 return sExecutor; 270 } 271 } 272