Inconsistent Message (IM)

ECn Categories > Non-informative Message (NIM) > Inconsistent Message (IM)

The displayed messages include explicit exception traces or error codes, i.e., there is no textual description of the error.

Amount of Issues App List
15 (A6) PressureNet, (A7) AnntenaPod, (A14) Wikimedia Commons, (A23) Prey, (A34) c:geo, (A38) Wake You In Music,

Examples

AntennaPod

Category: Video Players and Editors
v 4.6
Scenario: To add a podcast without internet connection
This video shows an example of a message with exception trace, at 3rd second the user using the sidebar enters to "Add Podcast" menu. At 12th second the user selects to search the new podcast via "Browse gpodder.net". But as response of that action the app shows a message in the center of the screen that says "An error ocurred:java.net.UnknownHostException ...", this message allows the user to know that something went wrong but due to the message shows the system error it can not be clear for all the users.

The following code snippets show the classes and files that are involved in the generation of the previuos issue.

To start reviewing this example we can see in the following snippet the initialization of a variable that will represent the subscribe button.

app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java

399.    subscribeButton = (Button) header.findViewById(R.id.butSubscribe);

As the variable is a button an onClickListener must be added, in this case this method at line 429 make a request to start downloading the podcast content calling the “downloadFeed” feed

app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java

    subscribeButton.setOnClickListener(v -> {
        if(feed != null && feedInFeedlist(feed)) {
            Intent intent = new Intent(OnlineFeedViewActivity.this, MainActivity.class);
            // feed.getId() is always 0, we have to retrieve the id from the feed list from
            // the database
            intent.putExtra(MainActivity.EXTRA_FEED_ID, getFeedId(feed));
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            startActivity(intent);
        } else {
            Feed f = new Feed(selectedDownloadUrl, null, feed.getTitle());
            f.setPreferences(feed.getPreferences());
            this.feed = f;
            try {
429.            DownloadRequester.getInstance().downloadFeed(this, f);
            } catch (DownloadRequestException e) {
                Log.e(TAG, Log.getStackTraceString(e));
                DownloadRequestErrorDialogCreator.newRequestErrorDialog(this, e.getMessage());
            }
            setSubscribeButtonState(feed);
        }
    });

In this class there are two methods “downloadFeed”, the first is a wrapper method that calls the following method with some predefined values

core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java

    public synchronized void downloadFeed(Context context, Feed feed) throws DownloadRequestException {
183.    downloadFeed(context, feed, false, false);
    }

The second one is in charge of validating some values before starting the download of a feed. When all the validations succeed it calls the method in charge of triggering the download at lines 177-178

core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java

    public synchronized void downloadFeed(Context context, Feed feed, boolean loadAllPages,
                                          boolean force)
            throws DownloadRequestException {
        if (feedFileValid(feed)) {
            String username = (feed.getPreferences() != null) ? feed.getPreferences().getUsername() : null;
            String password = (feed.getPreferences() != null) ? feed.getPreferences().getPassword() : null;
            String lastModified = feed.isPaged() || force ? null : feed.getLastUpdate();

            Bundle args = new Bundle();
            args.putInt(REQUEST_ARG_PAGE_NR, feed.getPageNr());
            args.putBoolean(REQUEST_ARG_LOAD_ALL_PAGES, loadAllPages);

177.        download(context, feed, null, new File(getFeedfilePath(context),
178.                getFeedfileName(feed)), true, username, password, lastModified, true, args);
        }
    }

Nevertheless the method called in the previous snippet is also a wrapper but in this case is for a simpler method. This method builds based on the parameters a new request and calls the simpler method at line 136.

core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java

    private void download(Context context, FeedFile item, FeedFile container, File dest,
                          boolean overwriteIfExists, String username, String password,
                          String lastModified, boolean deleteOnFailure, Bundle arguments) {
    ...
            DownloadRequest.Builder builder = new DownloadRequest.Builder(dest.toString(), item)
                .withAuthentication(username, password)
                .lastModified(lastModified)
                .deleteOnFailure(deleteOnFailure)
                .withArguments(arguments);
        DownloadRequest request = builder.build();
136.    download(context, request);
    }

Now that the previous method made the verifications needed this method take charge of triggering the download with the given DownloadRequest. It creates a new intent between lines 82-84 for the service that asynchronously retrieve the data.

core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java

    public synchronized boolean download(@NonNull Context context,
                                         @NonNull DownloadRequest request) {
        if (downloads.containsKey(request.getSource())) {
            if (BuildConfig.DEBUG) Log.i(TAG, "DownloadRequest is already stored.");
            return false;
        }
        downloads.put(request.getSource(), request);

82.     Intent launchIntent = new Intent(context, DownloadService.class);
83.     launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
84.     context.startService(launchIntent);

        return true;
    }

Now that the download request is made the DownloadService take the responsability. To start the following method is called on service start and depending on the value of the intent extra it add the download request to a queue or stop itself.

core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent.getParcelableExtra(EXTRA_REQUEST) != null) {
251.        onDownloadQueued(intent);
        } else if (numberOfDownloads.get() == 0) {
            stopSelf();
        }
        return Service.START_NOT_STICKY;
    }

Method in charge of queuing the request that are made to the service. At line 436 a new request is submitted to the “downloadExecutor”.

core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java

    private void onDownloadQueued(Intent intent) {
        Log.d(TAG, "Received enqueue request");
        DownloadRequest request = intent.getParcelableExtra(EXTRA_REQUEST);
        if (request == null) {
            throw new IllegalArgumentException(
                    "ACTION_ENQUEUE_DOWNLOAD intent needs request extra");
        }

        Downloader downloader = getDownloader(request);
        if (downloader != null) {
            numberOfDownloads.incrementAndGet();
            // smaller rss feeds before bigger media files
            if (request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
                downloads.add(0, downloader);
            } else {
                downloads.add(downloader);
            }
436.        downloadExecutor.submit(downloader);

            postDownloaders();
        }

        queryDownloads();
    }

The “downloadExecutor” is defined in the line 119 as a “CompletionService”

core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java

119. private CompletionService<Downloader> downloadExecutor;

In the other hand, the following snippet shows the most important line of the “onCreate” method of DownloadService, at line 293 a new thread is created in order to asynchronously execute the requests. The created thread is defined in the next snippet.

core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java

    @Override
    public void onCreate() {
    ...
293.    downloadCompletionThread.start();

At line 174 a new request is taked from the submitted on “onDownloadQueue” method. At line 178 the status of the request is saved in order to behave based on it.

core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java

    private Thread downloadCompletionThread = new Thread() {
        private static final String TAG = "downloadCompletionThd";

        @Override
        public void run() {
            Log.d(TAG, "downloadCompletionThread was started");
            while (!isInterrupted()) {
                try {
174.                Downloader downloader = downloadExecutor.take().get();
                    Log.d(TAG, "Received 'Download Complete' - message.");
                    removeDownload(downloader);
                    DownloadStatus status = downloader.getResult();
178.                boolean successful = status.isSuccessful();

Knowing that due to connectionless state the status of the request is not succesful, and also there is not an Unauthorized error, therefore the validations made in the following line and leads to the else statement that at line 200 save the status of the request.

core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java

    if (successful) {
    ...
    } else {
        numberOfDownloads.decrementAndGet();
        if (!status.isCancelled()) {
            if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) {
                ...
            } else {
                Log.e(TAG, "Download failed");
200.            saveDownloadStatus(status);
                handleFailedDownload(status, downloader.getDownloadRequest());

Adds a new DownloadStatus object to the list of completed downloads and saves it in the database

core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java

    private void saveDownloadStatus(DownloadStatus status) {
473.    reportQueue.add(status);
        DBWriter.addDownloadStatus(status);
    }

Validation to know if a report has to be generated, when its true a new notification is generated in the status bar for the user showing the summary of the download events.

core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java

    if (createReport) {
        Log.d(TAG, "Creating report");
        // create notification object
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
505.        .setTicker(getString(R.string.download_report_title))
            .setContentTitle(getString(R.string.download_report_content_title))
            .setContentText(
                    String.format(
509.                        getString(R.string.download_report_content),
                            successfulDownloads, failedDownloads)
            )
            .setSmallIcon(R.drawable.stat_notify_sync_error)
            .setLargeIcon(
                    BitmapFactory.decodeResource(getResources(),
                            R.drawable.stat_notify_sync_error)
            )
            .setContentIntent(
                    ClientConfig.downloadServiceCallbacks.getReportNotificationContentIntent(this)
            )
            .setAutoCancel(true);

Text used as title for the notification shown as a resume of the downloads

core/src/main/res/values/strings.xml

200. <string name="download_report_title">Downloads completed with error(s)</string>

Text used as content for the notification shown as a resume of the downloads

core/src/main/res/values/strings.xml

212. <string name="download_report_content">%1$d downloads succeeded, %2$d failed</string>