Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
      4 import static android.os.Build.VERSION_CODES.LOLLIPOP;
      5 
      6 import android.annotation.NonNull;
      7 import android.annotation.Nullable;
      8 import android.annotation.SuppressLint;
      9 import android.content.IntentSender;
     10 import android.content.IntentSender.SendIntentException;
     11 import android.content.pm.PackageInstaller;
     12 import android.os.Handler;
     13 import com.google.common.collect.ImmutableList;
     14 import java.io.IOException;
     15 import java.io.OutputStream;
     16 import java.util.HashMap;
     17 import java.util.HashSet;
     18 import java.util.List;
     19 import java.util.Map;
     20 import java.util.Set;
     21 import org.robolectric.RuntimeEnvironment;
     22 import org.robolectric.annotation.Implementation;
     23 import org.robolectric.annotation.Implements;
     24 import org.robolectric.shadow.api.Shadow;
     25 
     26 @Implements(value = PackageInstaller.class, minSdk = LOLLIPOP)
     27 @SuppressLint("NewApi")
     28 public class ShadowPackageInstaller {
     29 
     30   private int nextSessionId;
     31   private Map<Integer, PackageInstaller.SessionInfo> sessionInfos = new HashMap<>();
     32   private Map<Integer, PackageInstaller.Session> sessions = new HashMap<>();
     33   private Set<CallbackInfo> callbackInfos = new HashSet<>();
     34 
     35   private static class CallbackInfo {
     36     PackageInstaller.SessionCallback callback;
     37     Handler handler;
     38   }
     39 
     40   @Implementation
     41   protected List<PackageInstaller.SessionInfo> getAllSessions() {
     42     return ImmutableList.copyOf(sessionInfos.values());
     43   }
     44 
     45   @Implementation
     46   protected void registerSessionCallback(
     47       @NonNull PackageInstaller.SessionCallback callback, @NonNull Handler handler) {
     48     CallbackInfo callbackInfo = new CallbackInfo();
     49     callbackInfo.callback = callback;
     50     callbackInfo.handler = handler;
     51     this.callbackInfos.add(callbackInfo);
     52   }
     53 
     54   @Implementation
     55   @Nullable
     56   protected PackageInstaller.SessionInfo getSessionInfo(int sessionId) {
     57     return sessionInfos.get(sessionId);
     58   }
     59 
     60   @Implementation
     61   protected int createSession(@NonNull PackageInstaller.SessionParams params) throws IOException {
     62     final PackageInstaller.SessionInfo sessionInfo = new PackageInstaller.SessionInfo();
     63     sessionInfo.sessionId = nextSessionId++;
     64     sessionInfo.active = true;
     65     sessionInfo.appPackageName = params.appPackageName;
     66     sessionInfos.put(sessionInfo.getSessionId(), sessionInfo);
     67 
     68     for (final CallbackInfo callbackInfo : callbackInfos) {
     69       callbackInfo.handler.post(new Runnable() {
     70         @Override
     71         public void run() {
     72           callbackInfo.callback.onCreated(sessionInfo.sessionId);
     73         }
     74       });
     75     }
     76 
     77     return sessionInfo.sessionId;
     78   }
     79 
     80   @Implementation
     81   protected void abandonSession(int sessionId) {
     82     sessionInfos.remove(sessionId);
     83     sessions.remove(sessionId);
     84 
     85     for (final CallbackInfo callbackInfo : callbackInfos) {
     86       callbackInfo.handler.post(new Runnable() {
     87         @Override
     88         public void run() {
     89           callbackInfo.callback.onFinished(sessionId, false);
     90         }
     91       });
     92     }
     93   }
     94 
     95   @Implementation
     96   @NonNull
     97   protected PackageInstaller.Session openSession(int sessionId) throws IOException {
     98     if (!sessionInfos.containsKey(sessionId)) {
     99       throw new SecurityException("Invalid session Id: " + sessionId);
    100     }
    101 
    102     PackageInstaller.Session session = new PackageInstaller.Session(null);
    103     ShadowSession shadowSession = Shadow.extract(session);
    104     shadowSession.setShadowPackageInstaller(sessionId, this);
    105     sessions.put(sessionId, session);
    106     return session;
    107   }
    108 
    109   public void setSessionProgress(final int sessionId, final float progress) {
    110     for (final CallbackInfo callbackInfo : callbackInfos) {
    111       callbackInfo.handler.post(new Runnable() {
    112         @Override
    113         public void run() {
    114           callbackInfo.callback.onProgressChanged(sessionId, progress);
    115         }
    116       });
    117     }
    118   }
    119 
    120   /**
    121    * Prefer instead to use the Android APIs to close the session
    122    * {@link android.content.pm.PackageInstaller.Session#commit(IntentSender)}
    123    */
    124   @Deprecated
    125   public void setSessionSucceeds(int sessionId) {
    126     setSessionFinishes(sessionId, true);
    127   }
    128 
    129   public void setSessionFails(int sessionId) {
    130     setSessionFinishes(sessionId, false);
    131   }
    132 
    133   private void setSessionFinishes(final int sessionId, final boolean success) {
    134     for (final CallbackInfo callbackInfo : callbackInfos) {
    135       callbackInfo.handler.post(new Runnable() {
    136         @Override
    137         public void run() {
    138           callbackInfo.callback.onFinished(sessionId, success);
    139         }
    140       });
    141     }
    142 
    143     PackageInstaller.Session session = sessions.get(sessionId);
    144     ShadowSession shadowSession = Shadow.extract(session);
    145     if (success) {
    146       try {
    147         shadowSession.statusReceiver
    148             .sendIntent(RuntimeEnvironment.application, 0, null, null, null, null);
    149       } catch (SendIntentException e) {
    150         throw new RuntimeException(e);
    151       }
    152     }
    153   }
    154 
    155   @Implements(value = PackageInstaller.Session.class, minSdk = LOLLIPOP)
    156   public static class ShadowSession {
    157 
    158     private OutputStream outputStream;
    159     private boolean outputStreamOpen;
    160     private IntentSender statusReceiver;
    161     private int sessionId;
    162     private ShadowPackageInstaller shadowPackageInstaller;
    163 
    164     @Implementation(maxSdk = KITKAT_WATCH)
    165     protected void __constructor__() {}
    166 
    167     @Implementation
    168     @NonNull
    169     protected OutputStream openWrite(@NonNull String name, long offsetBytes, long lengthBytes)
    170         throws IOException {
    171       outputStream = new OutputStream() {
    172         @Override
    173         public void write(int aByte) throws IOException {
    174 
    175         }
    176 
    177         @Override
    178         public void close() throws IOException {
    179           outputStreamOpen = false;
    180         }
    181       };
    182       outputStreamOpen = true;
    183       return outputStream;
    184     }
    185 
    186     @Implementation
    187     protected void fsync(@NonNull OutputStream out) throws IOException {}
    188 
    189     @Implementation
    190     protected void commit(@NonNull IntentSender statusReceiver) {
    191       this.statusReceiver = statusReceiver;
    192       if (outputStreamOpen) {
    193         throw new SecurityException("OutputStream still open");
    194       }
    195 
    196       shadowPackageInstaller.setSessionSucceeds(sessionId);
    197     }
    198 
    199     @Implementation
    200     protected void close() {}
    201 
    202     @Implementation
    203     protected void abandon() {
    204       shadowPackageInstaller.abandonSession(sessionId);
    205     }
    206 
    207     private void setShadowPackageInstaller(int sessionId,
    208         ShadowPackageInstaller shadowPackageInstaller) {
    209       this.sessionId = sessionId;
    210       this.shadowPackageInstaller = shadowPackageInstaller;
    211     }
    212   }
    213 }
    214