blob: 52b51d243bd1ec67b295265a499882ee15d6ffbd [file] [log] [blame]
Robert Greenwalt4dded7a2016-12-05 16:33:32 -08001/*
2 * Copyright (C) 2017 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
17package android.telephony.mbms;
18
Brad Ebingerea737a82017-10-09 11:23:21 -070019import android.annotation.NonNull;
Hall Liuff6f9862017-07-28 16:37:57 -070020import android.annotation.SystemApi;
Hall Liu07f18432018-04-10 14:04:12 -070021import android.annotation.TestApi;
Hall Liu563e150c2017-04-25 15:04:26 -070022import android.content.Intent;
Robert Greenwalt4dded7a2016-12-05 16:33:32 -080023import android.net.Uri;
24import android.os.Parcel;
25import android.os.Parcelable;
Hall Liu2043f702017-06-19 17:20:10 -070026import android.util.Base64;
Hall Liu3bedd882017-06-29 14:10:48 -070027import android.util.Log;
Robert Greenwalt4dded7a2016-12-05 16:33:32 -080028
Hall Liu3bedd882017-06-29 14:10:48 -070029import java.io.ByteArrayInputStream;
30import java.io.ByteArrayOutputStream;
Hall Liu5b844872018-02-06 11:36:17 -080031import java.io.Externalizable;
32import java.io.File;
Hall Liu3bedd882017-06-29 14:10:48 -070033import java.io.IOException;
Hall Liu5b844872018-02-06 11:36:17 -080034import java.io.ObjectInput;
Hall Liu3bedd882017-06-29 14:10:48 -070035import java.io.ObjectInputStream;
Hall Liu5b844872018-02-06 11:36:17 -080036import java.io.ObjectOutput;
Hall Liu3bedd882017-06-29 14:10:48 -070037import java.io.ObjectOutputStream;
Hall Liu563e150c2017-04-25 15:04:26 -070038import java.net.URISyntaxException;
Hall Liu2043f702017-06-19 17:20:10 -070039import java.nio.charset.StandardCharsets;
40import java.security.MessageDigest;
41import java.security.NoSuchAlgorithmException;
Hall Liu9f116ef2017-06-23 16:32:30 -070042import java.util.Objects;
Hall Liu563e150c2017-04-25 15:04:26 -070043
Robert Greenwalt4dded7a2016-12-05 16:33:32 -080044/**
Hall Liuff6f9862017-07-28 16:37:57 -070045 * Describes a request to download files over cell-broadcast. Instances of this class should be
46 * created by the app when requesting a download, and instances of this class will be passed back
47 * to the app when the middleware updates the status of the download.
Robert Greenwalt4dded7a2016-12-05 16:33:32 -080048 */
Hall Liu39605ad2017-08-15 13:46:10 -070049public final class DownloadRequest implements Parcelable {
Hall Liu2043f702017-06-19 17:20:10 -070050 // Version code used to keep token calculation consistent.
51 private static final int CURRENT_VERSION = 1;
Hall Liu3bedd882017-06-29 14:10:48 -070052 private static final String LOG_TAG = "MbmsDownloadRequest";
53
Hall Liu39605ad2017-08-15 13:46:10 -070054 /** @hide */
Hall Liu3bedd882017-06-29 14:10:48 -070055 public static final int MAX_APP_INTENT_SIZE = 50000;
56
Hall Liu39605ad2017-08-15 13:46:10 -070057 /** @hide */
Hall Liu3bedd882017-06-29 14:10:48 -070058 public static final int MAX_DESTINATION_URI_SIZE = 50000;
Hall Liu2043f702017-06-19 17:20:10 -070059
Hall Liu563e150c2017-04-25 15:04:26 -070060 /** @hide */
Hall Liu5b844872018-02-06 11:36:17 -080061 private static class SerializationDataContainer implements Externalizable {
62 private String fileServiceId;
63 private Uri source;
64 private Uri destination;
65 private int subscriptionId;
66 private String appIntent;
67 private int version;
Hall Liu3bedd882017-06-29 14:10:48 -070068
Hall Liu5b844872018-02-06 11:36:17 -080069 public SerializationDataContainer() {}
70
71 SerializationDataContainer(DownloadRequest request) {
72 fileServiceId = request.fileServiceId;
73 source = request.sourceUri;
74 destination = request.destinationUri;
75 subscriptionId = request.subscriptionId;
76 appIntent = request.serializedResultIntentForApp;
77 version = request.version;
78 }
79
80 @Override
81 public void writeExternal(ObjectOutput objectOutput) throws IOException {
82 objectOutput.write(version);
83 objectOutput.writeUTF(fileServiceId);
84 objectOutput.writeUTF(source.toString());
85 objectOutput.writeUTF(destination.toString());
86 objectOutput.write(subscriptionId);
87 objectOutput.writeUTF(appIntent);
88 }
89
90 @Override
91 public void readExternal(ObjectInput objectInput) throws IOException {
92 version = objectInput.read();
93 fileServiceId = objectInput.readUTF();
94 source = Uri.parse(objectInput.readUTF());
95 destination = Uri.parse(objectInput.readUTF());
96 subscriptionId = objectInput.read();
97 appIntent = objectInput.readUTF();
98 // Do version checks here -- future versions may have other fields.
Hall Liu3bedd882017-06-29 14:10:48 -070099 }
100 }
101
Hall Liu563e150c2017-04-25 15:04:26 -0700102 public static class Builder {
Hall Liu3bedd882017-06-29 14:10:48 -0700103 private String fileServiceId;
Hall Liu563e150c2017-04-25 15:04:26 -0700104 private Uri source;
Hall Liu5b844872018-02-06 11:36:17 -0800105 private Uri destination;
Hall Liufcbf2402017-06-07 13:57:11 -0700106 private int subscriptionId;
Hall Liu563e150c2017-04-25 15:04:26 -0700107 private String appIntent;
Hall Liu2043f702017-06-19 17:20:10 -0700108 private int version = CURRENT_VERSION;
Hall Liu563e150c2017-04-25 15:04:26 -0700109
Hall Liu5b844872018-02-06 11:36:17 -0800110 /**
111 * Constructs a {@link Builder} from a {@link DownloadRequest}
112 * @param other The {@link DownloadRequest} from which the data for the {@link Builder}
113 * should come.
114 * @return An instance of {@link Builder} pre-populated with data from the provided
115 * {@link DownloadRequest}.
116 */
117 public static Builder fromDownloadRequest(DownloadRequest other) {
118 Builder result = new Builder(other.sourceUri, other.destinationUri)
119 .setServiceId(other.fileServiceId)
120 .setSubscriptionId(other.subscriptionId);
121 result.appIntent = other.serializedResultIntentForApp;
122 // Version of the result is going to be the current version -- as this class gets
123 // updated, new fields will be set to default values in here.
124 return result;
125 }
126
127 /**
128 * This method constructs a new instance of {@link Builder} based on the serialized data
129 * passed in.
130 * @param data A byte array, the contents of which should have been originally obtained
131 * from {@link DownloadRequest#toByteArray()}.
132 */
133 public static Builder fromSerializedRequest(byte[] data) {
134 Builder builder;
135 try {
136 ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(data));
137 SerializationDataContainer dataContainer =
138 (SerializationDataContainer) stream.readObject();
139 builder = new Builder(dataContainer.source, dataContainer.destination);
140 builder.version = dataContainer.version;
141 builder.appIntent = dataContainer.appIntent;
142 builder.fileServiceId = dataContainer.fileServiceId;
143 builder.subscriptionId = dataContainer.subscriptionId;
144 } catch (IOException e) {
145 // Really should never happen
146 Log.e(LOG_TAG, "Got IOException trying to parse opaque data");
147 throw new IllegalArgumentException(e);
148 } catch (ClassNotFoundException e) {
149 Log.e(LOG_TAG, "Got ClassNotFoundException trying to parse opaque data");
150 throw new IllegalArgumentException(e);
151 }
152 return builder;
153 }
Brad Ebingerea737a82017-10-09 11:23:21 -0700154
155 /**
156 * Builds a new DownloadRequest.
157 * @param sourceUri the source URI for the DownloadRequest to be built. This URI should
158 * never be null.
Hall Liu5b844872018-02-06 11:36:17 -0800159 * @param destinationUri The final location for the file(s) that are to be downloaded. It
160 * must be on the same filesystem as the temp file directory set via
161 * {@link android.telephony.MbmsDownloadSession#setTempFileRootDirectory(File)}.
162 * The provided path must be a directory that exists. An
163 * {@link IllegalArgumentException} will be thrown otherwise.
Brad Ebingerea737a82017-10-09 11:23:21 -0700164 */
Hall Liu5b844872018-02-06 11:36:17 -0800165 public Builder(@NonNull Uri sourceUri, @NonNull Uri destinationUri) {
166 if (sourceUri == null || destinationUri == null) {
167 throw new IllegalArgumentException("Source and destination URIs must be non-null.");
Brad Ebingerea737a82017-10-09 11:23:21 -0700168 }
169 source = sourceUri;
Hall Liu5b844872018-02-06 11:36:17 -0800170 destination = destinationUri;
Brad Ebingerea737a82017-10-09 11:23:21 -0700171 }
172
Hall Liud5c955f2017-07-20 15:32:51 -0700173 /**
174 * Sets the service from which the download request to be built will download from.
175 * @param serviceInfo
176 * @return
177 */
Hall Liu3bedd882017-06-29 14:10:48 -0700178 public Builder setServiceInfo(FileServiceInfo serviceInfo) {
179 fileServiceId = serviceInfo.getServiceId();
Hall Liu563e150c2017-04-25 15:04:26 -0700180 return this;
181 }
182
Hall Liu3bedd882017-06-29 14:10:48 -0700183 /**
Hall Liud5c955f2017-07-20 15:32:51 -0700184 * Set the service ID for the download request. For use by the middleware only.
Hall Liu3bedd882017-06-29 14:10:48 -0700185 * @hide
Hall Liu3bedd882017-06-29 14:10:48 -0700186 */
Hall Liu39605ad2017-08-15 13:46:10 -0700187 @SystemApi
Hall Liu07f18432018-04-10 14:04:12 -0700188 @TestApi
Hall Liu3bedd882017-06-29 14:10:48 -0700189 public Builder setServiceId(String serviceId) {
190 fileServiceId = serviceId;
Hall Liu563e150c2017-04-25 15:04:26 -0700191 return this;
192 }
193
Hall Liud5c955f2017-07-20 15:32:51 -0700194 /**
Hall Liud5c955f2017-07-20 15:32:51 -0700195 * Set the subscription ID on which the file(s) should be downloaded.
196 * @param subscriptionId
Hall Liud5c955f2017-07-20 15:32:51 -0700197 */
198 public Builder setSubscriptionId(int subscriptionId) {
199 this.subscriptionId = subscriptionId;
Hall Liu563e150c2017-04-25 15:04:26 -0700200 return this;
201 }
202
Hall Liud5c955f2017-07-20 15:32:51 -0700203 /**
204 * Set the {@link Intent} that should be sent when the download completes or fails. This
205 * should be an intent with a explicit {@link android.content.ComponentName} targeted to a
206 * {@link android.content.BroadcastReceiver} in the app's package.
207 *
208 * The middleware should not use this method.
209 * @param intent
Hall Liud5c955f2017-07-20 15:32:51 -0700210 */
Hall Liu563e150c2017-04-25 15:04:26 -0700211 public Builder setAppIntent(Intent intent) {
212 this.appIntent = intent.toUri(0);
Hall Liu3bedd882017-06-29 14:10:48 -0700213 if (this.appIntent.length() > MAX_APP_INTENT_SIZE) {
214 throw new IllegalArgumentException("App intent must not exceed length " +
215 MAX_APP_INTENT_SIZE);
216 }
Hall Liu563e150c2017-04-25 15:04:26 -0700217 return this;
218 }
219
220 public DownloadRequest build() {
Hall Liu5b844872018-02-06 11:36:17 -0800221 return new DownloadRequest(fileServiceId, source, destination,
222 subscriptionId, appIntent, version);
Hall Liu563e150c2017-04-25 15:04:26 -0700223 }
224 }
225
Hall Liu3bedd882017-06-29 14:10:48 -0700226 private final String fileServiceId;
Hall Liu563e150c2017-04-25 15:04:26 -0700227 private final Uri sourceUri;
Hall Liu5b844872018-02-06 11:36:17 -0800228 private final Uri destinationUri;
Hall Liufcbf2402017-06-07 13:57:11 -0700229 private final int subscriptionId;
Hall Liu563e150c2017-04-25 15:04:26 -0700230 private final String serializedResultIntentForApp;
Hall Liu2043f702017-06-19 17:20:10 -0700231 private final int version;
Hall Liu563e150c2017-04-25 15:04:26 -0700232
Hall Liu3bedd882017-06-29 14:10:48 -0700233 private DownloadRequest(String fileServiceId,
Hall Liu5b844872018-02-06 11:36:17 -0800234 Uri source, Uri destination, int sub,
Hall Liue373ee52017-09-08 18:02:38 -0700235 String appIntent, int version) {
Hall Liu3bedd882017-06-29 14:10:48 -0700236 this.fileServiceId = fileServiceId;
Robert Greenwalt4dded7a2016-12-05 16:33:32 -0800237 sourceUri = source;
Hall Liufcbf2402017-06-07 13:57:11 -0700238 subscriptionId = sub;
Hall Liu5b844872018-02-06 11:36:17 -0800239 destinationUri = destination;
Hall Liu563e150c2017-04-25 15:04:26 -0700240 serializedResultIntentForApp = appIntent;
Hall Liu2043f702017-06-19 17:20:10 -0700241 this.version = version;
Robert Greenwalt4dded7a2016-12-05 16:33:32 -0800242 }
243
Hall Liu563e150c2017-04-25 15:04:26 -0700244 private DownloadRequest(Parcel in) {
Hall Liu3bedd882017-06-29 14:10:48 -0700245 fileServiceId = in.readString();
Hall Liu563e150c2017-04-25 15:04:26 -0700246 sourceUri = in.readParcelable(getClass().getClassLoader());
Hall Liu5b844872018-02-06 11:36:17 -0800247 destinationUri = in.readParcelable(getClass().getClassLoader());
Hall Liufcbf2402017-06-07 13:57:11 -0700248 subscriptionId = in.readInt();
Hall Liu563e150c2017-04-25 15:04:26 -0700249 serializedResultIntentForApp = in.readString();
Hall Liu2043f702017-06-19 17:20:10 -0700250 version = in.readInt();
Hall Liu563e150c2017-04-25 15:04:26 -0700251 }
Robert Greenwalt4dded7a2016-12-05 16:33:32 -0800252
253 public int describeContents() {
254 return 0;
255 }
256
257 public void writeToParcel(Parcel out, int flags) {
Hall Liu3bedd882017-06-29 14:10:48 -0700258 out.writeString(fileServiceId);
Robert Greenwalt4dded7a2016-12-05 16:33:32 -0800259 out.writeParcelable(sourceUri, flags);
Hall Liu5b844872018-02-06 11:36:17 -0800260 out.writeParcelable(destinationUri, flags);
Hall Liufcbf2402017-06-07 13:57:11 -0700261 out.writeInt(subscriptionId);
Hall Liu563e150c2017-04-25 15:04:26 -0700262 out.writeString(serializedResultIntentForApp);
Hall Liu2043f702017-06-19 17:20:10 -0700263 out.writeInt(version);
Robert Greenwalt4dded7a2016-12-05 16:33:32 -0800264 }
265
Hall Liud5c955f2017-07-20 15:32:51 -0700266 /**
267 * @return The ID of the file service to download from.
268 */
Hall Liu3bedd882017-06-29 14:10:48 -0700269 public String getFileServiceId() {
270 return fileServiceId;
Hall Liu563e150c2017-04-25 15:04:26 -0700271 }
272
Hall Liud5c955f2017-07-20 15:32:51 -0700273 /**
274 * @return The source URI to download from
275 */
Hall Liu563e150c2017-04-25 15:04:26 -0700276 public Uri getSourceUri() {
277 return sourceUri;
278 }
279
Hall Liud5c955f2017-07-20 15:32:51 -0700280 /**
Hall Liu5b844872018-02-06 11:36:17 -0800281 * @return The destination {@link Uri} of the downloaded file.
282 */
283 public Uri getDestinationUri() {
284 return destinationUri;
285 }
286
287 /**
Hall Liud5c955f2017-07-20 15:32:51 -0700288 * @return The subscription ID on which to perform MBMS operations.
289 */
Hall Liufcbf2402017-06-07 13:57:11 -0700290 public int getSubscriptionId() {
291 return subscriptionId;
Hall Liu563e150c2017-04-25 15:04:26 -0700292 }
293
Hall Liud5c955f2017-07-20 15:32:51 -0700294 /**
295 * For internal use -- returns the intent to send to the app after download completion or
296 * failure.
297 * @hide
298 */
Hall Liu563e150c2017-04-25 15:04:26 -0700299 public Intent getIntentForApp() {
300 try {
301 return Intent.parseUri(serializedResultIntentForApp, 0);
302 } catch (URISyntaxException e) {
303 return null;
304 }
Robert Greenwalt4dded7a2016-12-05 16:33:32 -0800305 }
306
Hall Liu3bedd882017-06-29 14:10:48 -0700307 /**
Hall Liu5b844872018-02-06 11:36:17 -0800308 * This method returns a byte array that may be persisted to disk and restored to a
309 * {@link DownloadRequest}. The instance of {@link DownloadRequest} persisted by this method
310 * may be recovered via {@link Builder#fromSerializedRequest(byte[])}.
311 * @return A byte array of data to persist.
Hall Liu3bedd882017-06-29 14:10:48 -0700312 */
Hall Liu5b844872018-02-06 11:36:17 -0800313 public byte[] toByteArray() {
Hall Liu3bedd882017-06-29 14:10:48 -0700314 try {
315 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
316 ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);
Hall Liu5b844872018-02-06 11:36:17 -0800317 SerializationDataContainer container = new SerializationDataContainer(this);
Hall Liu3bedd882017-06-29 14:10:48 -0700318 stream.writeObject(container);
319 stream.flush();
320 return byteArrayOutputStream.toByteArray();
321 } catch (IOException e) {
322 // Really should never happen
323 Log.e(LOG_TAG, "Got IOException trying to serialize opaque data");
324 return null;
325 }
326 }
327
328 /** @hide */
Hall Liu2043f702017-06-19 17:20:10 -0700329 public int getVersion() {
330 return version;
331 }
332
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700333 public static final @android.annotation.NonNull Parcelable.Creator<DownloadRequest> CREATOR =
Robert Greenwalt4dded7a2016-12-05 16:33:32 -0800334 new Parcelable.Creator<DownloadRequest>() {
335 public DownloadRequest createFromParcel(Parcel in) {
336 return new DownloadRequest(in);
337 }
338 public DownloadRequest[] newArray(int size) {
339 return new DownloadRequest[size];
340 }
341 };
Hall Liu2043f702017-06-19 17:20:10 -0700342
343 /**
Hall Liu39605ad2017-08-15 13:46:10 -0700344 * Maximum permissible length for the app's destination path, when serialized via
345 * {@link Uri#toString()}.
346 */
347 public static int getMaxAppIntentSize() {
348 return MAX_APP_INTENT_SIZE;
349 }
350
351 /**
352 * Maximum permissible length for the app's download-completion intent, when serialized via
353 * {@link Intent#toUri(int)}.
354 */
355 public static int getMaxDestinationUriSize() {
356 return MAX_DESTINATION_URI_SIZE;
357 }
358
359 /**
Hall Liu2043f702017-06-19 17:20:10 -0700360 * Retrieves the hash string that should be used as the filename when storing a token for
361 * this DownloadRequest.
362 * @hide
363 */
364 public String getHash() {
365 MessageDigest digest;
366 try {
367 digest = MessageDigest.getInstance("SHA-256");
368 } catch (NoSuchAlgorithmException e) {
369 throw new RuntimeException("Could not get sha256 hash object");
370 }
371 if (version >= 1) {
Hall Liu5b844872018-02-06 11:36:17 -0800372 // Hash the source, destination, and the app intent
Hall Liu2043f702017-06-19 17:20:10 -0700373 digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
Hall Liu5b844872018-02-06 11:36:17 -0800374 digest.update(destinationUri.toString().getBytes(StandardCharsets.UTF_8));
Brad Ebingerea737a82017-10-09 11:23:21 -0700375 if (serializedResultIntentForApp != null) {
376 digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
377 }
Hall Liu2043f702017-06-19 17:20:10 -0700378 }
379 // Add updates for future versions here
380 return Base64.encodeToString(digest.digest(), Base64.URL_SAFE | Base64.NO_WRAP);
381 }
Hall Liu9f116ef2017-06-23 16:32:30 -0700382
383 @Override
384 public boolean equals(Object o) {
385 if (this == o) return true;
386 if (o == null) {
387 return false;
388 }
389 if (!(o instanceof DownloadRequest)) {
390 return false;
391 }
392 DownloadRequest request = (DownloadRequest) o;
Hall Liu3bedd882017-06-29 14:10:48 -0700393 return subscriptionId == request.subscriptionId &&
Hall Liu9f116ef2017-06-23 16:32:30 -0700394 version == request.version &&
Hall Liu3bedd882017-06-29 14:10:48 -0700395 Objects.equals(fileServiceId, request.fileServiceId) &&
Hall Liu9f116ef2017-06-23 16:32:30 -0700396 Objects.equals(sourceUri, request.sourceUri) &&
Hall Liu5b844872018-02-06 11:36:17 -0800397 Objects.equals(destinationUri, request.destinationUri) &&
Hall Liu9f116ef2017-06-23 16:32:30 -0700398 Objects.equals(serializedResultIntentForApp, request.serializedResultIntentForApp);
399 }
400
401 @Override
402 public int hashCode() {
Hall Liu5b844872018-02-06 11:36:17 -0800403 return Objects.hash(fileServiceId, sourceUri, destinationUri,
Hall Liu9f116ef2017-06-23 16:32:30 -0700404 subscriptionId, serializedResultIntentForApp, version);
405 }
Robert Greenwalt4dded7a2016-12-05 16:33:32 -0800406}