• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
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.google.android.libraries.mobiledatadownload.internal.util;
17 
18 import android.content.Context;
19 import android.net.Uri;
20 import androidx.annotation.VisibleForTesting;
21 import com.google.android.libraries.mobiledatadownload.SilentFeedback;
22 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidUri;
23 import com.google.android.libraries.mobiledatadownload.file.backends.BlobUri;
24 import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil;
25 import com.google.common.base.Optional;
26 import com.google.common.base.Preconditions;
27 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal.AllowedReaders;
28 import java.io.IOException;
29 import javax.annotation.Nullable;
30 
31 /** Utils to help with directory manipulation. */
32 public class DirectoryUtil {
33 
34   private static final String TAG = "DirectoryUtil";
35   // Correspond to MobStore Uri components.
36   public static final String MDD_STORAGE_MODULE = "datadownload";
37   public static final String MDD_MANIFEST_MODULE = "datadownloadmanifest";
38   public static final String MDD_STORAGE_ALL_GOOGLE_APPS = "public";
39   public static final String MDD_STORAGE_ONLY_GOOGLE_PLAY_SERVICES = "private";
40   public static final String MDD_STORAGE_SYMLINKS = "links";
41   @VisibleForTesting static final String MDD_STORAGE_ALL_APPS = "public_3p";
42 
43   /**
44    * Returns the top-level directory uri for all MDD downloads. Individual files should not be
45    * placed here; instead, use {@link #getDownloadDirectory(Context, AllowedReaders)}.
46    */
getBaseDownloadDirectory(Context context, Optional<String> instanceId)47   public static Uri getBaseDownloadDirectory(Context context, Optional<String> instanceId) {
48     AndroidUri.Builder builder =
49         AndroidUri.builder(context)
50             .setModule(
51                 instanceId != null && instanceId.isPresent()
52                     ? instanceId.get()
53                     : MDD_STORAGE_MODULE);
54 
55     if (instanceId != null && instanceId.isPresent()) {
56       builder.setRelativePath(MDD_STORAGE_MODULE);
57     }
58 
59     return builder.build();
60   }
61 
62   /**
63    * Returns the base directory where MDD stores manifest files. If instanceId is absent, a shared
64    * directory is returned; otherwise, a standalone directory with instanceId as its relative path
65    * is returned.
66    */
getManifestDirectory(Context context, Optional<String> instanceId)67   public static Uri getManifestDirectory(Context context, Optional<String> instanceId) {
68     Preconditions.checkNotNull(instanceId);
69 
70     return AndroidUri.builder(context)
71         .setModule(MDD_MANIFEST_MODULE)
72         .setRelativePath(instanceId.or(MDD_STORAGE_MODULE))
73         .build();
74   }
75 
76   /** Returns the directory uri for mdd download based on the allowed readers. */
getDownloadDirectory( Context context, AllowedReaders allowedReaders, Optional<String> instanceId)77   public static Uri getDownloadDirectory(
78       Context context, AllowedReaders allowedReaders, Optional<String> instanceId) {
79     String subDirectory = getSubDirectory(allowedReaders);
80     return getBaseDownloadDirectory(context, instanceId)
81         .buildUpon()
82         .appendPath(subDirectory)
83         .build();
84   }
85 
86   /** Returns the directory uri base for mdd symlinks. */
getBaseDownloadSymlinkDirectory(Context context, Optional<String> instanceId)87   public static Uri getBaseDownloadSymlinkDirectory(Context context, Optional<String> instanceId) {
88     return getBaseDownloadDirectory(context, instanceId)
89         .buildUpon()
90         .appendPath(MDD_STORAGE_SYMLINKS)
91         .build();
92   }
93 
94   /** Returns the directory uri for mdd symlinks based on the allowed readers. */
getDownloadSymlinkDirectory( Context context, AllowedReaders allowedReaders, Optional<String> instanceId)95   public static Uri getDownloadSymlinkDirectory(
96       Context context, AllowedReaders allowedReaders, Optional<String> instanceId) {
97     String subDirectory = getSubDirectory(allowedReaders);
98     return getBaseDownloadSymlinkDirectory(context, instanceId)
99         .buildUpon()
100         .appendPath(subDirectory)
101         .build();
102   }
103 
104   /**
105    * Returns the on device uri for the specified file.
106    *
107    * @param androidShared if sets to true, {@code getOnDeviceUri} returns the "blobstore" scheme
108    *     URI, otherwise it returns the "android" scheme URI.
109    */
110   // TODO(b/118137672): getOnDeviceUri shouldn't return null on error.
111 
112   @Nullable
getOnDeviceUri( Context context, AllowedReaders allowedReaders, String fileName, String checksum, SilentFeedback silentFeedback, Optional<String> instanceId, boolean androidShared)113   public static Uri getOnDeviceUri(
114       Context context,
115       AllowedReaders allowedReaders,
116       String fileName,
117       String checksum,
118       SilentFeedback silentFeedback,
119       Optional<String> instanceId,
120       boolean androidShared) {
121 
122     try {
123       if (androidShared) {
124         return getBlobUri(context, checksum);
125       }
126       Uri directoryUri = getDownloadDirectory(context, allowedReaders, instanceId);
127       return directoryUri.buildUpon().appendPath(fileName).build();
128     } catch (Exception e) {
129       // Catch all exceptions here as the above code can throw an exception if
130       // context.getFilesDir returns null.
131       LogUtil.e(e, "%s: Unable to create mobstore uri for file %s.", TAG, fileName);
132       silentFeedback.send(e, "Unable to create mobstore uri for file");
133 
134       return null;
135     }
136   }
137 
138   /**
139    * Returns the "blobstore" scheme URI of the file with final checksum {@code checksum}.
140    *
141    * <ul>
142    *   In order to be able to access the file in the blob store, the checksum needs to comply to the
143    *   following rules:
144    *   <li>at the moment, only checksums of type SHA256 are accepted.
145    *   <li>the checksum must be the file final checksum, i.e. after the download transforms have
146    *       been applied if any.
147    * </ul>
148    */
getBlobUri(Context context, String checksum)149   public static Uri getBlobUri(Context context, String checksum) throws IOException {
150     return BlobUri.builder(context).setBlobParameters(checksum).build();
151   }
152 
153   /**
154    * Returns the "blobstore" scheme URI used to acquire a lease on the file with final checksum
155    * {@code checksum}.
156    *
157    * <ul>
158    *   In order to be able to acquire the lease of the file in the blob store, the checksum needs to
159    *   comply to the following rules:
160    *   <li>at the moment, only checksums of type SHA256 are accepted.
161    *   <li>the checksum must be the file final checksum, i.e. for files with download_transform, it
162    *       should contain the transform of the file after the transforms have been applied.
163    * </ul>
164    */
getBlobStoreLeaseUri(Context context, String checksum, long expiryDateSecs)165   public static Uri getBlobStoreLeaseUri(Context context, String checksum, long expiryDateSecs)
166       throws IOException {
167     return BlobUri.builder(context).setLeaseParameters(checksum, expiryDateSecs).build();
168   }
169 
170   /**
171    * Returns the "blobstore" scheme URI used to release all the leases owned by the calling package.
172    */
getBlobStoreAllLeasesUri(Context context)173   public static Uri getBlobStoreAllLeasesUri(Context context) throws IOException {
174     return BlobUri.builder(context).setAllLeasesParameters().build();
175   }
176 
177   /**
178    * Returns {@code basename.extension}, with {@code instanceId} appended to basename if present.
179    *
180    * <p>Useful for building filenames that must be distinguished by InstanceId while keeping the
181    * same basename and file extension.
182    */
buildFilename( String basename, String extension, Optional<String> instanceId)183   public static String buildFilename(
184       String basename, String extension, Optional<String> instanceId) {
185     String resultBasename = basename;
186     if (instanceId != null && instanceId.isPresent()) {
187       resultBasename += instanceId.get();
188     }
189     return resultBasename + "." + extension;
190   }
191 
192   /** Convenience method to get the storage subdirectory based on the allowed readers. */
getSubDirectory(AllowedReaders allowedReaders)193   private static String getSubDirectory(AllowedReaders allowedReaders) {
194     switch (allowedReaders) {
195       case ALL_GOOGLE_APPS:
196         return MDD_STORAGE_ALL_GOOGLE_APPS;
197       case ONLY_GOOGLE_PLAY_SERVICES:
198         return MDD_STORAGE_ONLY_GOOGLE_PLAY_SERVICES;
199       case ALL_APPS:
200         return MDD_STORAGE_ALL_APPS;
201     }
202     throw new IllegalArgumentException("invalid allowed readers value");
203   }
204 }
205