Home | History | Annotate | Download | only in embmsdownload
      1 /*
      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 
     17 package com.android.phone.testapps.embmsdownload;
     18 
     19 import android.app.Activity;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.net.Uri;
     23 import android.os.Bundle;
     24 import android.os.Handler;
     25 import android.os.HandlerThread;
     26 import android.support.v7.widget.LinearLayoutManager;
     27 import android.support.v7.widget.RecyclerView;
     28 import android.telephony.MbmsDownloadSession;
     29 import android.telephony.SubscriptionManager;
     30 import android.telephony.mbms.DownloadProgressListener;
     31 import android.telephony.mbms.DownloadRequest;
     32 import android.telephony.mbms.DownloadStatusListener;
     33 import android.telephony.mbms.FileInfo;
     34 import android.telephony.mbms.FileServiceInfo;
     35 import android.telephony.mbms.MbmsDownloadSessionCallback;
     36 import android.util.Log;
     37 import android.view.View;
     38 import android.view.ViewGroup;
     39 import android.widget.ArrayAdapter;
     40 import android.widget.Button;
     41 import android.widget.ImageView;
     42 import android.widget.NumberPicker;
     43 import android.widget.Spinner;
     44 import android.widget.TextView;
     45 import android.widget.Toast;
     46 
     47 import java.io.File;
     48 import java.io.IOException;
     49 import java.util.ArrayList;
     50 import java.util.Collections;
     51 import java.util.List;
     52 
     53 public class EmbmsTestDownloadApp extends Activity {
     54     private static final String LOG_TAG = "EmbmsDownloadApp";
     55 
     56     public static final String DOWNLOAD_DONE_ACTION =
     57             "com.android.phone.testapps.embmsdownload.DOWNLOAD_DONE";
     58 
     59     private static final String CUSTOM_EMBMS_TEMP_FILE_LOCATION = "customEmbmsTempFiles";
     60 
     61     private static final String FILE_AUTHORITY = "com.android.phone.testapps";
     62     private static final String FILE_DOWNLOAD_SCHEME = "filedownload";
     63 
     64     private static EmbmsTestDownloadApp sInstance;
     65 
     66     private static final class ImageAdapter
     67             extends RecyclerView.Adapter<ImageAdapter.ImageViewHolder> {
     68         static class ImageViewHolder extends RecyclerView.ViewHolder {
     69             public ImageView imageView;
     70             public ImageViewHolder(ImageView view) {
     71                 super(view);
     72                 imageView = view;
     73             }
     74         }
     75 
     76         private final List<Uri> mImageUris = new ArrayList<>();
     77 
     78         @Override
     79         public ImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
     80             ImageView view = new ImageView(parent.getContext());
     81             view.setAdjustViewBounds(true);
     82             view.setMaxHeight(500);
     83             return new ImageViewHolder(view);
     84         }
     85 
     86         @Override
     87         public void onBindViewHolder(ImageViewHolder holder, int position) {
     88             holder.imageView.setImageURI(mImageUris.get(position));
     89         }
     90 
     91         @Override
     92         public int getItemCount() {
     93             return mImageUris.size();
     94         }
     95 
     96         public void addImage(Uri uri) {
     97             mImageUris.add(uri);
     98             notifyDataSetChanged();
     99         }
    100     }
    101 
    102     private final class FileServiceInfoAdapter
    103             extends ArrayAdapter<FileServiceInfo> {
    104         public FileServiceInfoAdapter(Context context) {
    105             super(context, android.R.layout.simple_spinner_item);
    106             setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    107         }
    108 
    109         @Override
    110         public View getView(int position, View convertView, ViewGroup parent) {
    111             FileServiceInfo info = getItem(position);
    112             TextView result = new TextView(EmbmsTestDownloadApp.this);
    113             result.setText(info.getNameForLocale(info.getLocales().get(0)));
    114             return result;
    115         }
    116 
    117         @Override
    118         public View getDropDownView(int position, View convertView, ViewGroup parent) {
    119             FileServiceInfo info = getItem(position);
    120             TextView result = new TextView(EmbmsTestDownloadApp.this);
    121             String text = "name="
    122                     + info.getNameForLocale(info.getLocales().get(0))
    123                     + ", "
    124                     + "numFiles="
    125                     + info.getFiles().size();
    126             result.setText(text);
    127             return result;
    128         }
    129 
    130         public void update(List<FileServiceInfo> services) {
    131             clear();
    132             addAll(services);
    133         }
    134     }
    135 
    136     private final class DownloadRequestAdapter
    137             extends ArrayAdapter<DownloadRequest> {
    138         public DownloadRequestAdapter(Context context) {
    139             super(context, android.R.layout.simple_spinner_item);
    140             setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    141         }
    142 
    143         @Override
    144         public View getView(int position, View convertView, ViewGroup parent) {
    145             DownloadRequest request = getItem(position);
    146             TextView result = new TextView(EmbmsTestDownloadApp.this);
    147             result.setText(request.getSourceUri().toSafeString());
    148             return result;
    149         }
    150 
    151         @Override
    152         public View getDropDownView(int position, View convertView, ViewGroup parent) {
    153             return getView(position, convertView, parent);
    154         }
    155     }
    156 
    157 
    158     private MbmsDownloadSessionCallback mCallback = new MbmsDownloadSessionCallback() {
    159         @Override
    160         public void onError(int errorCode, String message) {
    161             runOnUiThread(() -> Toast.makeText(EmbmsTestDownloadApp.this,
    162                     "Error " + errorCode + ": " + message, Toast.LENGTH_SHORT).show());
    163         }
    164 
    165         @Override
    166         public void onFileServicesUpdated(List<FileServiceInfo> services) {
    167             EmbmsTestDownloadApp.this.runOnUiThread(() ->
    168                     Toast.makeText(EmbmsTestDownloadApp.this,
    169                             "Got services length " + services.size(),
    170                             Toast.LENGTH_SHORT).show());
    171             updateFileServicesList(services);
    172         }
    173 
    174         @Override
    175         public void onMiddlewareReady() {
    176             runOnUiThread(() -> Toast.makeText(EmbmsTestDownloadApp.this,
    177                     "Initialization done", Toast.LENGTH_SHORT).show());
    178         }
    179     };
    180 
    181     private MbmsDownloadSession mDownloadManager;
    182     private Handler mHandler;
    183     private HandlerThread mHandlerThread;
    184     private FileServiceInfoAdapter mFileServiceInfoAdapter;
    185     private DownloadRequestAdapter mDownloadRequestAdapter;
    186     private ImageAdapter mImageAdapter;
    187 
    188     @Override
    189     protected void onCreate(Bundle savedInstanceState) {
    190         super.onCreate(savedInstanceState);
    191         setContentView(R.layout.activity_main);
    192 
    193         sInstance = this;
    194         mHandlerThread = new HandlerThread("EmbmsDownloadWorker");
    195         mHandlerThread.start();
    196         mHandler = new Handler(mHandlerThread.getLooper());
    197         mFileServiceInfoAdapter = new FileServiceInfoAdapter(this);
    198         mDownloadRequestAdapter = new DownloadRequestAdapter(this);
    199 
    200         RecyclerView downloadedImages = (RecyclerView) findViewById(R.id.downloaded_images);
    201         downloadedImages.setLayoutManager(
    202                 new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
    203         mImageAdapter = new ImageAdapter();
    204         downloadedImages.setAdapter(mImageAdapter);
    205 
    206         Button bindButton = (Button) findViewById(R.id.bind_button);
    207         bindButton.setOnClickListener((view) -> {
    208             mDownloadManager = MbmsDownloadSession.create(this, mHandler::post, mCallback);
    209         });
    210 
    211         Button setTempFileRootButton = (Button) findViewById(R.id.set_temp_root_button);
    212         setTempFileRootButton.setOnClickListener((view) -> {
    213             File downloadDir = new File(EmbmsTestDownloadApp.this.getFilesDir(),
    214                     CUSTOM_EMBMS_TEMP_FILE_LOCATION);
    215             downloadDir.mkdirs();
    216             mDownloadManager.setTempFileRootDirectory(downloadDir);
    217             Toast.makeText(EmbmsTestDownloadApp.this,
    218                     "temp file root set to " + downloadDir, Toast.LENGTH_SHORT).show();
    219         });
    220 
    221         Button getFileServicesButton = (Button) findViewById(R.id.get_file_services_button);
    222         getFileServicesButton.setOnClickListener((view) -> mHandler.post(() -> {
    223             mDownloadManager.requestUpdateFileServices(Collections.singletonList("Class1"));
    224         }));
    225 
    226         final Spinner serviceSelector = (Spinner) findViewById(R.id.available_file_services);
    227         serviceSelector.setAdapter(mFileServiceInfoAdapter);
    228 
    229         Button requestDlButton = (Button) findViewById(R.id.request_dl_button);
    230         requestDlButton.setOnClickListener((view) ->  {
    231             if (mDownloadManager == null) {
    232                 Toast.makeText(EmbmsTestDownloadApp.this,
    233                         "No download service bound", Toast.LENGTH_SHORT).show();
    234                 return;
    235             }
    236             FileServiceInfo serviceInfo =
    237                     (FileServiceInfo) serviceSelector.getSelectedItem();
    238             if (serviceInfo == null) {
    239                 Toast.makeText(EmbmsTestDownloadApp.this,
    240                         "No file service selected", Toast.LENGTH_SHORT).show();
    241                 return;
    242             }
    243 
    244             performDownload(serviceInfo);
    245         });
    246 
    247         Button requestCleanupButton = (Button) findViewById(R.id.request_cleanup_button);
    248         requestCleanupButton.setOnClickListener((view) ->
    249                 SideChannel.triggerCleanup(EmbmsTestDownloadApp.this));
    250 
    251         Button requestSpuriousTempFilesButton =
    252                 (Button) findViewById(R.id.request_spurious_temp_files_button);
    253         requestSpuriousTempFilesButton.setOnClickListener((view) ->
    254                 SideChannel.requestSpuriousTempFiles(EmbmsTestDownloadApp.this,
    255                         (FileServiceInfo) serviceSelector.getSelectedItem()));
    256 
    257         NumberPicker downloadDelayPicker = (NumberPicker) findViewById(R.id.delay_factor);
    258         downloadDelayPicker.setMinValue(1);
    259         downloadDelayPicker.setMaxValue(50);
    260 
    261         Button delayDownloadButton = (Button) findViewById(R.id.delay_download_button);
    262         delayDownloadButton.setOnClickListener((view) ->
    263                 SideChannel.delayDownloads(EmbmsTestDownloadApp.this,
    264                         downloadDelayPicker.getValue()));
    265 
    266         final Spinner downloadRequestSpinner = (Spinner) findViewById(R.id.active_downloads);
    267         downloadRequestSpinner.setAdapter(mDownloadRequestAdapter);
    268 
    269         Button cancelDownloadButton = (Button) findViewById(R.id.cancel_download_button);
    270         cancelDownloadButton.setOnClickListener((view) -> {
    271             if (mDownloadManager == null) {
    272                 Toast.makeText(EmbmsTestDownloadApp.this,
    273                         "No download service bound", Toast.LENGTH_SHORT).show();
    274                 return;
    275             }
    276             DownloadRequest request =
    277                     (DownloadRequest) downloadRequestSpinner.getSelectedItem();
    278             mDownloadManager.cancelDownload(request);
    279             mDownloadRequestAdapter.remove(request);
    280         });
    281 
    282         Button registerProgressCallback =
    283                 (Button) findViewById(R.id.register_progress_callback_button);
    284         registerProgressCallback.setOnClickListener((view) -> {
    285             if (mDownloadManager == null) {
    286                 Toast.makeText(EmbmsTestDownloadApp.this,
    287                         "No download service bound", Toast.LENGTH_SHORT).show();
    288                 return;
    289             }
    290             DownloadRequest req = (DownloadRequest) downloadRequestSpinner.getSelectedItem();
    291             if (req == null) {
    292                 Toast.makeText(EmbmsTestDownloadApp.this,
    293                         "No DownloadRequest Pending for progress...", Toast.LENGTH_SHORT).show();
    294                 return;
    295             }
    296             mDownloadManager.addProgressListener(req, sInstance.getMainThreadHandler()::post,
    297                     new DownloadProgressListener() {
    298                         @Override
    299                         public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo,
    300                                 int currentDownloadSize, int fullDownloadSize,
    301                                 int currentDecodedSize, int fullDecodedSize) {
    302                             Toast.makeText(EmbmsTestDownloadApp.this,
    303                                     "Progress Updated (" + fileInfo + ") cd: " + currentDecodedSize
    304                                             + " fd: " + fullDownloadSize, Toast.LENGTH_SHORT)
    305                                     .show();
    306                         }
    307                     });
    308         });
    309 
    310         Button registerStateCallback =
    311                 (Button) findViewById(R.id.register_state_callback_button);
    312         registerStateCallback.setOnClickListener((view) -> {
    313             if (mDownloadManager == null) {
    314                 Toast.makeText(EmbmsTestDownloadApp.this,
    315                         "No download service bound", Toast.LENGTH_SHORT).show();
    316                 return;
    317             }
    318             DownloadRequest req = (DownloadRequest) downloadRequestSpinner.getSelectedItem();
    319             if (req == null) {
    320                 Toast.makeText(EmbmsTestDownloadApp.this,
    321                         "No DownloadRequest Pending for state...", Toast.LENGTH_SHORT).show();
    322                 return;
    323             }
    324             mDownloadManager.addStatusListener(req, sInstance.getMainThreadHandler()::post,
    325                     new DownloadStatusListener() {
    326                         @Override
    327                         public void onStatusUpdated(DownloadRequest request, FileInfo fileInfo,
    328                                 @MbmsDownloadSession.DownloadStatus int state) {
    329                             Toast.makeText(EmbmsTestDownloadApp.this,
    330                                     "State Updated (" + fileInfo + ") state: " + state,
    331                                     Toast.LENGTH_SHORT).show();
    332                         }
    333                     });
    334         });
    335 
    336         Button registerAllCallbacks =
    337                 (Button) findViewById(R.id.register_all_callback_button);
    338         registerAllCallbacks.setOnClickListener((view) -> {
    339             if (mDownloadManager == null) {
    340                 Toast.makeText(EmbmsTestDownloadApp.this,
    341                         "No download service bound", Toast.LENGTH_SHORT).show();
    342                 return;
    343             }
    344             DownloadRequest req = (DownloadRequest) downloadRequestSpinner.getSelectedItem();
    345             if (req == null) {
    346                 Toast.makeText(EmbmsTestDownloadApp.this,
    347                         "No DownloadRequest Pending for state...", Toast.LENGTH_SHORT).show();
    348                 return;
    349             }
    350 
    351             mDownloadManager.addStatusListener(req, sInstance.getMainThreadHandler()::post,
    352                     new DownloadStatusListener() {
    353                         @Override
    354                         public void onStatusUpdated(DownloadRequest request, FileInfo fileInfo,
    355                                 @MbmsDownloadSession.DownloadStatus int state) {
    356                             Toast.makeText(EmbmsTestDownloadApp.this,
    357                                     "State Updated (" + fileInfo + ") state: " + state,
    358                                     Toast.LENGTH_SHORT).show();
    359                         }
    360                     });
    361 
    362             mDownloadManager.addProgressListener(req, sInstance.getMainThreadHandler()::post,
    363                     new DownloadProgressListener() {
    364                         @Override
    365                         public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo,
    366                                 int currentDownloadSize, int fullDownloadSize,
    367                                 int currentDecodedSize, int fullDecodedSize) {
    368                             Toast.makeText(EmbmsTestDownloadApp.this,
    369                                     "Progress Updated (" + fileInfo + ") cd: " + currentDecodedSize
    370                                             + " fd: " + fullDownloadSize, Toast.LENGTH_SHORT)
    371                                     .show();
    372                         }
    373                     });
    374         });
    375     }
    376 
    377     @Override
    378     protected void onDestroy() {
    379         super.onDestroy();
    380         mHandlerThread.quit();
    381         sInstance = null;
    382     }
    383 
    384     public static EmbmsTestDownloadApp getInstance() {
    385         return sInstance;
    386     }
    387 
    388     public void onDownloadFailed(int result) {
    389         runOnUiThread(() ->
    390                 Toast.makeText(this, "Download failed: " + result, Toast.LENGTH_SHORT).show());
    391     }
    392 
    393     // TODO: assumes that process does not get killed. Replace with more robust alternative
    394     public void onDownloadDone(Uri fileLocation) {
    395         Log.i(LOG_TAG, "File completed: " + fileLocation);
    396         File imageFile = new File(fileLocation.getPath());
    397         if (!imageFile.exists()) {
    398             Toast.makeText(this, "Download done but destination doesn't exist", Toast.LENGTH_SHORT)
    399                     .show();
    400             return;
    401         }
    402         mImageAdapter.addImage(fileLocation);
    403     }
    404 
    405     private void updateFileServicesList(List<FileServiceInfo> services) {
    406         runOnUiThread(() -> mFileServiceInfoAdapter.update(services));
    407     }
    408 
    409     private void performDownload(FileServiceInfo info) {
    410         Uri.Builder sourceUriBuilder = new Uri.Builder()
    411                 .scheme(FILE_DOWNLOAD_SCHEME)
    412                 .authority(FILE_AUTHORITY);
    413         if (info.getServiceId().contains("2")) {
    414             sourceUriBuilder.path("/*");
    415         } else {
    416             sourceUriBuilder.path("/image.png");
    417         }
    418 
    419         Intent completionIntent = new Intent(DOWNLOAD_DONE_ACTION);
    420         completionIntent.setClass(this, DownloadCompletionReceiver.class);
    421 
    422         DownloadRequest request = new DownloadRequest.Builder(sourceUriBuilder.build(),
    423                 getDestination(info.getServiceId()))
    424                 .setServiceInfo(info)
    425                 .setAppIntent(completionIntent)
    426                 .setSubscriptionId(SubscriptionManager.getDefaultSubscriptionId())
    427                 .build();
    428 
    429         mDownloadManager.download(request);
    430         mDownloadRequestAdapter.add(request);
    431     }
    432 
    433     private Uri getDestination(String serviceId) {
    434         File dest;
    435         try {
    436             if (serviceId.contains("2")) {
    437                 dest = new File(getFilesDir().getCanonicalFile(), "images/animals/");
    438                 if (!dest.exists()) {
    439                     dest.mkdirs();
    440                 }
    441             } else {
    442                 dest = new File(getFilesDir().getCanonicalFile(), "images/");
    443                 if (!dest.exists()) {
    444                     dest.mkdirs();
    445                 }
    446             }
    447             return Uri.fromFile(dest);
    448         } catch (IOException e) {
    449             throw new RuntimeException(e);
    450         }
    451     }
    452 }
    453