package org.robolectric.shadows; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.IntentSender; import android.content.pm.IPackageInstaller; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.os.Handler; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import java.io.IOException; import java.io.OutputStream; import java.util.*; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static org.robolectric.Shadows.shadowOf; @Implements(value = PackageInstaller.class, minSdk = LOLLIPOP) public class ShadowPackageInstaller { private int nextSessionId; private Map<Integer, PackageInstaller.SessionInfo> sessionInfos = new HashMap<>(); private Map<Integer, PackageInstaller.Session> sessions = new HashMap<>(); private Set<CallbackInfo> callbackInfos = new HashSet<>(); private class CallbackInfo { PackageInstaller.SessionCallback callback; Handler handler; } @Implementation public List<PackageInstaller.SessionInfo> getAllSessions() { return ImmutableList.copyOf(sessionInfos.values()); } @Implementation public void registerSessionCallback(@NonNull PackageInstaller.SessionCallback callback, @NonNull Handler handler) { CallbackInfo callbackInfo = new CallbackInfo(); callbackInfo.callback = callback; callbackInfo.handler = handler; this.callbackInfos.add(callbackInfo); } @Implementation @Nullable public PackageInstaller.SessionInfo getSessionInfo(int sessionId) { return sessionInfos.get(sessionId); } @Implementation public int createSession(@NonNull PackageInstaller.SessionParams params) throws IOException { final PackageInstaller.SessionInfo sessionInfo = new PackageInstaller.SessionInfo(); sessionInfo.sessionId = nextSessionId++; sessionInfo.active = true; sessionInfo.appPackageName = params.appPackageName; sessionInfos.put(sessionInfo.getSessionId(), sessionInfo); for (final CallbackInfo callbackInfo : callbackInfos) { callbackInfo.handler.post(new Runnable() { @Override public void run() { callbackInfo.callback.onCreated(sessionInfo.sessionId); } }); } return sessionInfo.sessionId; } @Implementation public void abandonSession(int sessionId) { sessionInfos.remove(sessionId); } @Implementation @NonNull public PackageInstaller.Session openSession(int sessionId) throws IOException { if (!sessionInfos.containsKey(sessionId)) { throw new SecurityException("Invalid session Id: " + sessionId); } PackageInstaller.Session session = new PackageInstaller.Session(null); sessions.put(sessionId, session); return session; } public void setSessionProgress(final int sessionId, final float progress) { for (final CallbackInfo callbackInfo : callbackInfos) { callbackInfo.handler.post(new Runnable() { @Override public void run() { callbackInfo.callback.onProgressChanged(sessionId, progress); } }); } } public void setSessionSucceeds(int sessionId) throws Exception { setSessionFinishes(sessionId, true); } public void setSessionFails(int sessionId) throws Exception { setSessionFinishes(sessionId, false); } private void setSessionFinishes(final int sessionId, final boolean success) throws Exception { for (final CallbackInfo callbackInfo : callbackInfos) { callbackInfo.handler.post(new Runnable() { @Override public void run() { callbackInfo.callback.onFinished(sessionId, success); } }); } PackageInstaller.Session session = sessions.get(sessionId); ShadowSession shadowSession = shadowOf(session); shadowSession.statusReceiver .sendIntent(RuntimeEnvironment.application, 0, null, null, null, null); } @Implements(value = PackageInstaller.Session.class, minSdk = LOLLIPOP) public static class ShadowSession { private OutputStream outputStream; private boolean outputStreamOpen; private IntentSender statusReceiver; public void __constructor__() { } @Implementation public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes, long lengthBytes) throws IOException { outputStream = new OutputStream() { @Override public void write(int aByte) throws IOException { } @Override public void close() throws IOException { outputStreamOpen = false; } }; outputStreamOpen = true; return outputStream; } @Implementation public void fsync(@NonNull OutputStream out) throws IOException { } @Implementation public void commit(@NonNull IntentSender statusReceiver) { this.statusReceiver = statusReceiver; if (outputStreamOpen) { throw new SecurityException("OutputStream still open"); } } @Implementation public void close() { } } }