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