public class BackgroundAlarmListener implements WakefulIntentService.AlarmListener {
    @Inject UserStorage mUserStorage;

    public void scheduleAlarms(AlarmManager mgr, PendingIntent pi, Context context) {
        App.with(context).getAppComponent().inject(this);

        final String refreshInterval = mUserStorage.getRefreshInterval();
        if (!TextUtils.isEmpty(refreshInterval)) {
            int refreshIntervalMinutes = Integer.parseInt(refreshInterval);
            if (refreshIntervalMinutes > 0) {
                final long triggerAtMillis = SystemClock.elapsedRealtime() + 1000;
                final long interval = TimeUnit.MINUTES.toMillis(refreshIntervalMinutes);
                mgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                        triggerAtMillis,
                        interval,
                        pi);
            } else if (refreshIntervalMinutes == -1) {
                WakefulIntentService.cancelAlarms(context);
            }
        }
    }

    public void sendWakefulWork(Context context) {
        WakefulIntentService.sendWakefulWork(context,
                RetrieveService.getFromBackgroundSyncIntent(context));
    }

    @Override public long getMaxAge(Context context) {
        return (AlarmManager.INTERVAL_FIFTEEN_MINUTES * 2);
    }
} public class BasicAuthorisationRequestInterceptorBuilder {
    private Encoder mEncoder;

    public BasicAuthorisationRequestInterceptorBuilder(Encoder encoder) {
        mEncoder = encoder;
    }

    public RequestInterceptor build(final String credentials) {
        return new RequestInterceptor() {
            @Override public void intercept(RequestInterceptor.RequestFacade request) {
                request.addHeader(Constants.AUTHORIZATION,
                        String.format("Basic %s", mEncoder.encode(credentials.getBytes())));
            }
        };
    }
} public class DelegatingConverter implements Converter {
    private final SubscriptionConverter mSubscriptionConverter;
    private final MarkAsReadConverter mMarkAsReadConverter;
    private final CommentsConverter mCommentsConverter;
    private final TokenConverter mTokenConverter;
    private final PostConverter mPostConverter;
    private final Converter mOriginalConverter;

    public DelegatingConverter(Converter originalConverter, TokenConverter tokenConverter,
                               PostConverter postConverter, MarkAsReadConverter markAsReadConverter,
                               SubscriptionConverter subscriptionConverter,
                               CommentsConverter commentsConverter) {
        mSubscriptionConverter = subscriptionConverter;
        mMarkAsReadConverter = markAsReadConverter;
        mOriginalConverter = originalConverter;
        mCommentsConverter = commentsConverter;
        mTokenConverter = tokenConverter;
        mPostConverter = postConverter;
    }

    @Override public Object fromBody(TypedInput body, Type type) throws ConversionException {
        if (type == Token.class) {
            return mTokenConverter.fromBody(body, type);
        } else if (type == MarkAllRead.class) {
            return mMarkAsReadConverter.fromBody(body, type);
        } else if (type == SubscriptionResponse.class) {
            return mSubscriptionConverter.fromBody(body, type);
        } else if (isListOfDesiredType(type, Post.class)) {
            return mPostConverter.fromBody(body, type);
        } else if (isListOfDesiredType(type, Comment.class)) {
            return mCommentsConverter.fromBody(body, type);
        }

        return mOriginalConverter.fromBody(body, type);
    }

    private boolean isListOfDesiredType(Type type, Type desiredType) {
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            return parameterizedType.getActualTypeArguments()[0] == desiredType;
        }
        return false;
    }

    @Override public TypedOutput toBody(Object object) {
        return mOriginalConverter.toBody(object);
    }
} public class ImageDownloader {
    private static final int WATCH_SCREEN_SIZE = 400;
    private static final int MARKER = 65536;

    public byte[] downloadImage(String imageUrl) {
        byte[] bytes = null;
        try {
            URL url = new URL(imageUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setDoInput(true);
            connection.connect();

            MarkableInputStream markStream = new MarkableInputStream(connection.getInputStream());
            long mark = markStream.savePosition(MARKER);

            final BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(markStream, null, options);

            options.inSampleSize = calculateInSampleSize(options,
                    WATCH_SCREEN_SIZE,
                    WATCH_SCREEN_SIZE);
            options.inJustDecodeBounds = false;

            markStream.reset(mark);

            final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            Bitmap bitmap = BitmapFactory.decodeStream(markStream, null, options);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream);

            bytes = byteStream.toByteArray();
            bitmap.recycle();
        } catch (Exception e) {
            Timber.d(e, "Failed to download image");
        }

        return bytes;
    }

    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth,
                                             int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
} public class LatestPostsRetriever {
    private final RedditService mRedditService;

    private final ImageDownloader mImageDownloader;
    private final UserStorage mUserStorage;

    public LatestPostsRetriever(@NonNull final ImageDownloader imageDownloader,
                                @NonNull final UserStorage userStorage,
                                @NonNull final RedditService redditService) {
        mImageDownloader = imageDownloader;
        mUserStorage = userStorage;
        mRedditService = redditService;
    }

    @NonNull public Observable<List<PostAndImage>> retrieve() {
        final long currentSavedTimestamp = mUserStorage.getTimestamp();
        return Observable.defer(() -> {
            String subreddits = mUserStorage.getSubreddits();
            if (StringUtils.isEmpty(subreddits)) {
                subreddits = StringUtils.join("+", Constants.sDefaultSelectedSubreddits);
            }
            return mRedditService.latestPosts(subreddits,
                    mUserStorage.getSortType(),
                    mUserStorage.getNumberToRequest())
                    .doOnNext(posts -> {
                        long maxTimestamp = 0;
                        for (final Post p : posts) {
                            if (p.getCreatedUtc() > maxTimestamp) {
                                maxTimestamp = p.getCreatedUtc();
                            }
                        }
                        mUserStorage.setSeenTimestamp(maxTimestamp);
                    })
                    .flatMap(Observable::from)
                    .filter(post -> post.getCreatedUtc() > currentSavedTimestamp)
                    .flatMap(post -> Observable.defer(() -> {
                        final PostAndImage postAndImage = new PostAndImage(post);
                        if (post.hasImageUrl()) {
                            final byte[] bytes = mImageDownloader.downloadImage(post.getImageUrl());
                            if (bytes != null) {
                                postAndImage.setImage(Asset.createFromBytes(bytes));
                            }
                        }
                        return Observable.just(postAndImage);
                    }).onErrorResumeNext(throwable -> Observable.empty()))
                    .toList();
        }).onErrorResumeNext(throwable -> {
            Timber.e(throwable, "Failed to get latest posts");
            // If we fail somewhere whilst retrieving posts, just emit an empty list
            return Observable.just(Collections.<PostAndImage>emptyList());
        });
    }

    static class PostAndImage {
        private final Post mPost;
        private Asset mImage;

        PostAndImage(Post post) {
            mPost = post;
        }

        public void setImage(Asset image) {
            mImage = image;
        }

        public Asset getImage() {
            return mImage;
        }

        public Post getPost() {
            return mPost;
        }
    }
} public class PostConverter implements Converter {
    private final GsonConverter mOriginalConverter;
    private final UserStorage mUserStorage;
    private final HtmlDecoder mHtmlDecoder;
    private final Resources mResources;

    public PostConverter(GsonConverter gsonConverter, Resources resources, UserStorage userStorage,
                         HtmlDecoder htmlDecoder) {
        mOriginalConverter = gsonConverter;
        mResources = resources;
        mUserStorage = userStorage;
        mHtmlDecoder = htmlDecoder;
    }

    @Override public Object fromBody(TypedInput body, Type type) throws ConversionException {
        ListingResponse listingResponse = (ListingResponse) mOriginalConverter.fromBody(body,
                ListingResponse.class);
        return new ListingResponseConverter(mUserStorage, mResources, mHtmlDecoder).convert(
                listingResponse);
    }

    @Override public TypedOutput toBody(Object object) {
        return mOriginalConverter.toBody(object);
    }
} public interface RedditAuthenticationService {
    @POST("/api/v1/access_token") @Headers("Content-Type: application/x-www-form-urlencoded")
    @FormUrlEncoded Observable<Token> loginToken(@Field("grant_type") String grantType,
                                                 @Field("redirect_uri") String redirectUri,
                                                 @Field("code") String code);

    @POST("/api/v1/access_token") @Headers("Content-Type: application/x-www-form-urlencoded")
    @FormUrlEncoded Token refreshToken(@Field("grant_type") String grantType,
                                       @Field("refresh_token") String refreshToken);

    @POST("/api/v1/access_token") @Headers("Content-Type: application/x-www-form-urlencoded")
    @FormUrlEncoded Token appOnlyToken(@Field("grant_type") String grantType,
                                       @Field("device_id") String deviceId);
} public interface RedditService {
    @POST("/api/comment?api_type=json") Observable<RedditResponse> commentOnPost(
            @Query("text") String comment, @Query("thing_id") String postId);

    @GET("/subreddits/mine/subscriber.json?limit=1000")
    Observable<SubscriptionResponse> subredditSubscriptions();

    @GET("/message/unread.json") Observable<List<Post>> unreadMessages();

    @GET("/{permalink}.json") Observable<List<Comment>> comments(
            @Path(value = "permalink", encode = false) String permalink,
            @Query("sort") String sort);

    @GET("/r/{subreddit}/{sort}.json") Observable<List<Post>> latestPosts(
            @Path("subreddit") String subreddit, @Path("sort") String sort,
            @Query("limit") Integer limit);

    @POST("/api/vote") Observable<Void> vote(@Query("id") String fullname,
                                             @Query("dir") Integer voteDirection);

    @POST("/api/read_all_messages") MarkAllRead markAllMessagesRead();

    @POST("/api/compose?api_type=json") Observable<RedditResponse> replyToDirectMessage(
            @Query("subject") String subject, @Query("text") String message,
            @Query("to") String toUser);
} public class TokenConverter implements Converter {
    private final Converter mOriginalConverter;

    public TokenConverter(Converter originalConverter) {
        mOriginalConverter = originalConverter;
    }

    @Override public Object fromBody(TypedInput body, Type type) throws ConversionException {
        if (type != Token.class) {
            return mOriginalConverter.fromBody(body, type);
        }

        // We don't check for null refresh token, it's optional - it's retrieved on initial auth but not on a token refresh
        TokenResponse response = (TokenResponse) mOriginalConverter.fromBody(body,
                TokenResponse.class);
        if (response == null || response.getAccessToken() == null) {
            throw new ConversionException("Empty/missing token response: " + response);
        }
        return new Token.Builder().accessToken(response.getAccessToken())
                .refreshToken(response.getRefreshToken())
                .expiryTimeMillis(DateTime.now(DateTimeZone.UTC)
                        .plusSeconds(response.getExpiresIn())
                        .getMillis())
                .build();
    }

    @Override public TypedOutput toBody(Object object) {
        return mOriginalConverter.toBody(object);
    }
} public class TokenRefreshInterceptor implements Interceptor {
    private static final String BEARER_FORMAT = "bearer %s";

    private final TokenStorage mTokenStorage;
    private final RedditAuthenticationService mAuthenticationService;

    public TokenRefreshInterceptor(TokenStorage tokenStorage,
                                   RedditAuthenticationService authenticationService) {
        mTokenStorage = tokenStorage;
        mAuthenticationService = authenticationService;
    }

    @Override public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response;
        if (request.url().toString().contains("access_token")) {
            // if we're trying to get the access token, carry on
            response = chain.proceed(request);
        } else if (mTokenStorage.hasNoToken()) {
            // User hasn't logged in, request an app only token
            response = requestAppOnlyTokenAndProceed(chain, request);
        } else if (mTokenStorage.hasTokenExpired()) {
            // Token's expired, renew it first
            response = renewTokenAndProceed(chain, request);
        } else {
            response = makeRequest(chain, request);
        }

        return response;
    }

    // synchronized so we only renew one request at a time
    @NonNull private synchronized Response requestAppOnlyTokenAndProceed(Chain chain,
                                                                         Request originalRequest) throws
            IOException {
        try {
            Token token = mAuthenticationService.appOnlyToken(Constants.GRANT_TYPE_INSTALLED_CLIENT,
                    UUID.randomUUID().toString());
            mTokenStorage.updateToken(token);
        } catch (RetrofitError error) {
            if (error.getResponse() == null || isServerError(error.getResponse())) {
                throw new RuntimeException(
                        "Failed to retrieve app only token, empty response/server error: " + error.getCause());
            } else {
                throw new RuntimeException("Failed to retrieve app only token, unknown cause: " + error
                        .getCause());
            }
        }
        return addHeaderAndProceedWithChain(chain, originalRequest);
    }

    @NonNull
    private synchronized Response renewTokenAndProceed(Chain chain, Request originalRequest) throws
            IOException {
        if (mTokenStorage.hasTokenExpired()) {
            try {
                Token token = mAuthenticationService.refreshToken(Constants.GRANT_TYPE_REFRESH_TOKEN,
                        mTokenStorage.getRefreshToken());
                mTokenStorage.updateToken(token);
            } catch (RetrofitError error) {
                if (error.getResponse() == null || isServerError(error.getResponse())) {
                    throw new RuntimeException(
                            "Failed to renew token, empty response/server error: " + error.getCause());
                } else {
                    throw new RuntimeException("Failed to renew token, unknown cause: " + error.getCause());
                }
            }
        }
        return addHeaderAndProceedWithChain(chain, originalRequest);
    }

    @NonNull private Response makeRequest(Chain chain, Request request) throws IOException {
        Response r = addHeaderAndProceedWithChain(chain, request);
        if (r.code() == HttpURLConnection.HTTP_FORBIDDEN || r.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
            mTokenStorage.forceExpireToken();
            // throw an IOException, so that this request will be retried
            throw new IOException("Token problem, throwing to retry");
        }
        return r;
    }

    @NonNull
    private Response addHeaderAndProceedWithChain(Chain chain, Request originalRequest) throws
            IOException {
        final String value = String.format(BEARER_FORMAT, mTokenStorage.getAccessToken());
        final Request authenticatedRequest = originalRequest.newBuilder()
                .header(Constants.AUTHORIZATION, value)
                .build();
        return chain.proceed(authenticatedRequest);
    }

    private boolean isServerError(retrofit.client.Response response) {
        return response.getStatus() == HttpURLConnection.HTTP_NOT_FOUND || response.getStatus() == HttpURLConnection.HTTP_INTERNAL_ERROR;
    }
} public class UnreadDirectMessageRetriever {
    private final RedditService mRedditService;

    private final TokenStorage mTokenStorage;
    private final UserStorage mUserStorage;

    public UnreadDirectMessageRetriever(@NonNull final TokenStorage tokenStorage,
                                        @NonNull final UserStorage userStorage,
                                        @NonNull final RedditService redditService) {
        mTokenStorage = tokenStorage;
        mUserStorage = userStorage;
        mRedditService = redditService;
    }

    @NonNull public Observable<List<Post>> retrieve() {
        return Observable.defer(() -> {
            if (mTokenStorage.isLoggedIn() && mUserStorage.messagesEnabled()) {
                return mRedditService.unreadMessages().flatMap(posts -> {
                    if (!posts.isEmpty()) {
                        MarkAllRead markAllRead = mRedditService.markAllMessagesRead();
                        if (markAllRead.hasErrors()) {
                            throw new RuntimeException("Failed to mark all messages as read: " + markAllRead);
                        }
                    }

                    return Observable.just(posts);
                });
            } else {
                return Observable.just(Collections.<Post>emptyList());
            }
        }).onErrorResumeNext(throwable -> {
            Timber.e(throwable, "UnreadDirectMessageRetriever: failed to get unread messages");
            // If we fail somewhere whilst retrieving messages, just emit an empty list
            return Observable.just(Collections.<Post>emptyList());
        });
    }
} public class Base64Encoder implements Encoder {
    public static final int ENCODING_FLAGS = Base64.NO_WRAP;

    public String encode(byte[] bytes) {
        return Base64.encodeToString(bytes, ENCODING_FLAGS);
    }

    @Override public byte[] decode(String string) {
        return Base64.decode(string, ENCODING_FLAGS);
    }
} public class BrowserIntentBuilder {
    private static final String DEFAULT_URL = "https://www.google.com";

    private static final String PACKAGE_NAME = "packageName";
    private static final String SIMPLE_NAME = "simpleName";
    private static final String CLASS_NAME = "className";

    private final PackageManager mPackageManager;

    @Inject public BrowserIntentBuilder(PackageManager packageManager) {
        mPackageManager = packageManager;
    }

    @Nullable public Intent build(String title, Intent target) {
        // find all the browsers installed - don't use the reddit url because that will include 3rd party reddit clients
        final Intent dummy = new Intent(Intent.ACTION_VIEW);
        dummy.setData(Uri.parse(DEFAULT_URL));

        final List<ResolveInfo> resInfo = mPackageManager.queryIntentActivities(dummy, 0);

        final List<HashMap<String, String>> metaInfo = new ArrayList<>();
        for (ResolveInfo ri : resInfo) {
            if (ri.activityInfo == null) {
                continue;
            }

            final HashMap<String, String> info = new HashMap<>();
            info.put(PACKAGE_NAME, ri.activityInfo.packageName);
            info.put(CLASS_NAME, ri.activityInfo.name);
            info.put(SIMPLE_NAME, String.valueOf(ri.activityInfo.loadLabel(mPackageManager)));
            metaInfo.add(info);
        }

        if (metaInfo.isEmpty()) {
            return null;
        }

        // sort items by display name
        Collections.sort(metaInfo, new Comparator<HashMap<String, String>>() {
            @Override
            public int compare(HashMap<String, String> map, HashMap<String, String> map2) {
                return map.get(SIMPLE_NAME).compareTo(map2.get(SIMPLE_NAME));
            }
        });

        // create the custom intent list
        final List<Intent> targetedIntents = new ArrayList<>();
        for (HashMap<String, String> mi : metaInfo) {
            Intent targetedShareIntent = (Intent) target.clone();
            targetedShareIntent.setPackage(mi.get(PACKAGE_NAME));
            targetedShareIntent.setClassName(mi.get(PACKAGE_NAME), mi.get(CLASS_NAME));
            targetedIntents.add(targetedShareIntent);
        }

        final Intent chooserIntent = Intent.createChooser(targetedIntents.get(0), title);
        targetedIntents.remove(0);

        final Parcelable[] targetedIntentsParcelable = targetedIntents.toArray(new Parcelable[targetedIntents
                .size()]);
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntentsParcelable);

        return chooserIntent;
    }
} public class Constants {
    public final static Set<String> sDefaultSelectedSubreddits = new LinkedHashSet<>(Arrays.asList(
            "todayilearned",
            "AskReddit"));

    public static final String WEB_URL_REDDIT = "https://www.reddit.com";
    public static final String ENDPOINT_URL_SSL_REDDIT = "https://ssl.reddit.com/";
    public static final String ENDPOINT_URL_OAUTH_REDDIT = "https://oauth.reddit.com/";

    public static final String AUTHORIZATION = "Authorization";

    public static final String GRANT_TYPE_AUTHORISATION_CODE = "authorization_code";
    public static final String GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
    public static final String GRANT_TYPE_INSTALLED_CLIENT = "https://oauth.reddit.com/grants/installed_client";

    public static final String PATH_REDDIT_POSTS = "/redditwear";
    public static final String PATH_REFRESH = "/refresh";
    public static final String PATH_OPEN_ON_PHONE = "/openonphone";
    public static final String PATH_REPLY = "/replytopost";
    public static final String PATH_SAVE_TO_POCKET = "/send_to_pocket";
    public static final String PATH_VOTE = "/vote";
    public static final String PATH_COMMENTS = "/comments";
    public static final String PATH_NO_NEW_POSTS = "/no_new_posts";
    public static final String PATH_LOGGING = "/logging";

    public static final String KEY_REDDIT_POSTS = "posts";
    public static final String KEY_POST_PERMALINK = "post_permalink";

    public static final String PATH_POST_REPLY_RESULT_SUCCESS = "/post_reply_success";
    public static final String PATH_POST_REPLY_RESULT_FAILURE = "/post_reply_failure";

    public static final String PATH_KEY_MESSAGE = "post_message";
    public static final String PATH_KEY_POST_FULLNAME = "post_fullname";
    public static final String PATH_KEY_IS_DIRECT_MESSAGE = "is_direct";
    public static final String PATH_KEY_MESSAGE_SUBJECT = "msg_subject";
    public static final String PATH_KEY_MESSAGE_TO_USER = "msg_to_user";

    public static final String PATH_SAVE_TO_POCKET_RESULT_SUCCESS = "/save_pocket_success";
    public static final String PATH_SAVE_TO_POCKET_RESULT_FAILED = "/save_pocket_failed";

    public static final String PATH_VOTE_RESULT_SUCCESS = "/vote_success";
    public static final String PATH_VOTE_RESULT_FAILED = "/vote_failed";
    public static final String PATH_GET_COMMENTS_RESULT_FAILED = "/getting_comments";

    public static final String KEY_CONFIRMATION_MESSAGE = "confirm_message";
    public static final String KEY_PATH = "receiver_path";
    public static final String KEY_POST_VOTE_DIRECTION = "vote_direction";
    public static final String KEY_CONFIRMATION_ANIMATION = "key_confirm_animation";
    public static final String KEY_DISMISS_AFTER_ACTION = "open_on_phone_dismisses";
    public static final String KEY_NOTIFICATION_ID = "notification_id";
    public static final String KEY_ACTION_ORDER = "action_order";
    public static final String KEY_HIGHRES_IMAGE_NAME = "view_image";

    public static final int ACTION_ORDER_VIEW_COMMENTS = 0;
    public static final int ACTION_ORDER_REPLY = 1;
    public static final int ACTION_ORDER_UPVOTE = 2;
    public static final int ACTION_ORDER_DOWNVOTE = 3;
    public static final int ACTION_ORDER_SAVE_TO_POCKET = 4;
    public static final int ACTION_ORDER_OPEN_ON_PHONE = 5;
    public static final int ACTION_ORDER_VIEW_IMAGE = 6;
} public interface UserStorage {
    int getNumberToRequest();

    void setSeenTimestamp(long timestamp);
    void clearTimestamp();

    long getTimestamp();

    String getSortType();
    String getSubreddits();
    Set<String> getSubredditCollection();
    String getRefreshInterval();

    boolean messagesEnabled();
    boolean downloadFullSizedImages();
    boolean openOnPhoneDismissesAfterAction();
}
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.preference.PreferenceManager;
import com.emmaguy.todayilearned.refresh.BackgroundAlarmListener;
import com.emmaguy.todayilearned.refresh.BasicAuthorisationRequestInterceptorBuilder;
import com.emmaguy.todayilearned.refresh.CommentsConverter;
import com.emmaguy.todayilearned.refresh.DelegatingConverter;
import com.emmaguy.todayilearned.refresh.HtmlDecoder;
import com.emmaguy.todayilearned.refresh.ImageDownloader;
import com.emmaguy.todayilearned.refresh.LatestPostsRetriever;
import com.emmaguy.todayilearned.refresh.MarkAsReadConverter;
import com.emmaguy.todayilearned.refresh.PostConverter;
import com.emmaguy.todayilearned.refresh.RedditAuthenticationService;
import com.emmaguy.todayilearned.refresh.RedditService;
import com.emmaguy.todayilearned.refresh.SubscriptionConverter;
import com.emmaguy.todayilearned.refresh.TokenConverter;
import com.emmaguy.todayilearned.refresh.TokenRefreshInterceptor;
import com.emmaguy.todayilearned.refresh.UnreadDirectMessageRetriever;
import com.emmaguy.todayilearned.settings.Base64Encoder;
import com.emmaguy.todayilearned.settings.BrowserIntentBuilder;
import com.emmaguy.todayilearned.sharedlib.Constants;
import com.emmaguy.todayilearned.storage.TokenStorage;
import com.emmaguy.todayilearned.storage.UserStorage;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.google.gson.Gson;
import com.squareup.okhttp.OkHttpClient;
import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import retrofit.RestAdapter;
import retrofit.client.OkClient;
import retrofit.converter.GsonConverter;
import rx.Scheduler;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
package com.emmaguy.todayilearned;





@Module public class AppModule {
    private final Context mContext;

    public AppModule(Context context) {
        mContext = context;
    }

    @Provides @Singleton public Context provideApplicationContext() {
        return mContext;
    }

    @Provides @Singleton public Resources provideResources() {
        return mContext.getResources();
    }

    @Provides @Singleton public SharedPreferences provideSharedPreferences() {
        return PreferenceManager.getDefaultSharedPreferences(mContext);
    }

    @Provides @Singleton public ImageDownloader provideImageDownloader() {
        return new ImageDownloader();
    }

    @Provides @Singleton
    public LatestPostsRetriever provideLatestPostsFromRedditRetriever(ImageDownloader downloader,
                                                                      UserStorage storage,
                                                                      RedditService redditService) {
        return new LatestPostsRetriever(downloader, storage, redditService);
    }

    @Provides @Singleton public UnreadDirectMessageRetriever provideUnreadDirectMessageRetriever(
            TokenStorage tokenStorage, UserStorage storage, RedditService redditService) {
        return new UnreadDirectMessageRetriever(tokenStorage, storage, redditService);
    }

    @Provides @Singleton public BrowserIntentBuilder provideBrowserIntentBuilder(Context context) {