• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
getAllSessions()41   protected List<PackageInstaller.SessionInfo> getAllSessions() {
42     return ImmutableList.copyOf(sessionInfos.values());
43   }
44 
45   @Implementation
registerSessionCallback( @onNull PackageInstaller.SessionCallback callback, @NonNull Handler handler)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
getSessionInfo(int sessionId)56   protected PackageInstaller.SessionInfo getSessionInfo(int sessionId) {
57     return sessionInfos.get(sessionId);
58   }
59 
60   @Implementation
createSession(@onNull PackageInstaller.SessionParams params)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
abandonSession(int sessionId)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
openSession(int sessionId)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 
setSessionProgress(final int sessionId, final float progress)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
setSessionSucceeds(int sessionId)125   public void setSessionSucceeds(int sessionId) {
126     setSessionFinishes(sessionId, true);
127   }
128 
setSessionFails(int sessionId)129   public void setSessionFails(int sessionId) {
130     setSessionFinishes(sessionId, false);
131   }
132 
setSessionFinishes(final int sessionId, final boolean success)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)
__constructor__()165     protected void __constructor__() {}
166 
167     @Implementation
168     @NonNull
openWrite(@onNull String name, long offsetBytes, long lengthBytes)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
fsync(@onNull OutputStream out)187     protected void fsync(@NonNull OutputStream out) throws IOException {}
188 
189     @Implementation
commit(@onNull IntentSender statusReceiver)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
close()200     protected void close() {}
201 
202     @Implementation
abandon()203     protected void abandon() {
204       shadowPackageInstaller.abandonSession(sessionId);
205     }
206 
setShadowPackageInstaller(int sessionId, ShadowPackageInstaller shadowPackageInstaller)207     private void setShadowPackageInstaller(int sessionId,
208         ShadowPackageInstaller shadowPackageInstaller) {
209       this.sessionId = sessionId;
210       this.shadowPackageInstaller = shadowPackageInstaller;
211     }
212   }
213 }
214