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