Merge "Add PrepareUpdateService."

am: 2e33cbeb20

Change-Id: I5f62286c5454ab5e44d73e99daad3345c1841024
This commit is contained in:
Zhomart Mukhamejanov 2018-12-17 14:29:31 -08:00 committed by android-build-merger
commit 1811a6b734
7 changed files with 110 additions and 113 deletions

View file

@ -33,7 +33,7 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<service android:name=".services.PrepareStreamingService"/> <service android:name=".services.PrepareUpdateService"/>
</application> </application>
</manifest> </manifest>

View file

@ -220,7 +220,7 @@ privileged system app, so it's granted the required permissions to access
- [x] Add Sample app update state (separate from update_engine status) - [x] Add Sample app update state (separate from update_engine status)
- [x] Add smart update completion detection using onStatusUpdate - [x] Add smart update completion detection using onStatusUpdate
- [x] Add pause/resume demo - [x] Add pause/resume demo
- [x] Verify system partition checksum for package - [-] Verify system partition checksum for package
## Running tests ## Running tests
@ -235,8 +235,8 @@ privileged system app, so it's granted the required permissions to access
5. Run a test file 5. Run a test file
``` ```
adb shell am instrument \ adb shell am instrument \
-w com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner \ -w -e class com.example.android.systemupdatersample.UpdateManagerTest#applyUpdate_appliesPayloadToUpdateEngine \
-c com.example.android.systemupdatersample.util.PayloadSpecsTest com.example.android.systemupdatersample.tests/android.support.test.runner.AndroidJUnitRunner
``` ```

View file

@ -17,19 +17,18 @@
package com.example.android.systemupdatersample; package com.example.android.systemupdatersample;
import android.content.Context; import android.content.Context;
import android.os.Handler;
import android.os.UpdateEngine; import android.os.UpdateEngine;
import android.os.UpdateEngineCallback; import android.os.UpdateEngineCallback;
import android.util.Log; import android.util.Log;
import com.example.android.systemupdatersample.services.PrepareStreamingService; import com.example.android.systemupdatersample.services.PrepareUpdateService;
import com.example.android.systemupdatersample.util.PayloadSpecs;
import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes; import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes;
import com.example.android.systemupdatersample.util.UpdateEngineProperties; import com.example.android.systemupdatersample.util.UpdateEngineProperties;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.AtomicDouble; import com.google.common.util.concurrent.AtomicDouble;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -50,11 +49,10 @@ public class UpdateManager {
private static final String TAG = "UpdateManager"; private static final String TAG = "UpdateManager";
/** HTTP Header: User-Agent; it will be sent to the server when streaming the payload. */ /** HTTP Header: User-Agent; it will be sent to the server when streaming the payload. */
private static final String HTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " static final String HTTP_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
+ "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"; + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36";
private final UpdateEngine mUpdateEngine; private final UpdateEngine mUpdateEngine;
private final PayloadSpecs mPayloadSpecs;
private AtomicInteger mUpdateEngineStatus = private AtomicInteger mUpdateEngineStatus =
new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE); new AtomicInteger(UpdateEngine.UpdateStatusConstants.IDLE);
@ -84,9 +82,15 @@ public class UpdateManager {
private final UpdateManager.UpdateEngineCallbackImpl private final UpdateManager.UpdateEngineCallbackImpl
mUpdateEngineCallback = new UpdateManager.UpdateEngineCallbackImpl(); mUpdateEngineCallback = new UpdateManager.UpdateEngineCallbackImpl();
public UpdateManager(UpdateEngine updateEngine, PayloadSpecs payloadSpecs) { private final Handler mHandler;
/**
* @param updateEngine UpdateEngine instance.
* @param handler Handler for {@link PrepareUpdateService} intent service.
*/
public UpdateManager(UpdateEngine updateEngine, Handler handler) {
this.mUpdateEngine = updateEngine; this.mUpdateEngine = updateEngine;
this.mPayloadSpecs = payloadSpecs; this.mHandler = handler;
} }
/** /**
@ -293,45 +297,17 @@ public class UpdateManager {
mManualSwitchSlotRequired.set(false); mManualSwitchSlotRequired.set(false);
} }
if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) { Log.d(TAG, "Starting PrepareUpdateService");
applyAbNonStreamingUpdate(config); PrepareUpdateService.startService(context, config, mHandler, (code, payloadSpec) -> {
} else { if (code != PrepareUpdateService.RESULT_CODE_SUCCESS) {
applyAbStreamingUpdate(context, config); Log.e(TAG, "PrepareUpdateService failed, result code is " + code);
} setUpdaterStateSilent(UpdaterState.ERROR);
}
private void applyAbNonStreamingUpdate(UpdateConfig config)
throws UpdaterState.InvalidTransitionException {
UpdateData.Builder builder = UpdateData.builder()
.setExtraProperties(prepareExtraProperties(config));
try {
builder.setPayload(mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile()));
} catch (IOException e) {
Log.e(TAG, "Error creating payload spec", e);
setUpdaterState(UpdaterState.ERROR);
return; return;
} }
updateEngineApplyPayload(builder.build()); updateEngineApplyPayload(UpdateData.builder()
} .setExtraProperties(prepareExtraProperties(config))
.setPayload(payloadSpec)
private void applyAbStreamingUpdate(Context context, UpdateConfig config) { .build());
UpdateData.Builder builder = UpdateData.builder()
.setExtraProperties(prepareExtraProperties(config));
Log.d(TAG, "Starting PrepareStreamingService");
PrepareStreamingService.startService(context, config, (code, payloadSpec) -> {
if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) {
builder.setPayload(payloadSpec);
builder.addExtraProperty("USER_AGENT=" + HTTP_USER_AGENT);
config.getAbConfig()
.getAuthorization()
.ifPresent(s -> builder.addExtraProperty("AUTHORIZATION=" + s));
updateEngineApplyPayload(builder.build());
} else {
Log.e(TAG, "PrepareStreamingService failed, result code is " + code);
setUpdaterStateSilent(UpdaterState.ERROR);
}
}); });
} }
@ -343,6 +319,12 @@ public class UpdateManager {
// User will enable it manually by clicking "Switch Slot" button on the screen. // User will enable it manually by clicking "Switch Slot" button on the screen.
extraProperties.add(UpdateEngineProperties.PROPERTY_DISABLE_SWITCH_SLOT_ON_REBOOT); extraProperties.add(UpdateEngineProperties.PROPERTY_DISABLE_SWITCH_SLOT_ON_REBOOT);
} }
if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_STREAMING) {
extraProperties.add("USER_AGENT=" + HTTP_USER_AGENT);
config.getAbConfig()
.getAuthorization()
.ifPresent(s -> extraProperties.add("AUTHORIZATION=" + s));
}
return extraProperties; return extraProperties;
} }
@ -555,7 +537,6 @@ public class UpdateManager {
} }
/** /**
*
* Contains update data - PayloadSpec and extra properties list. * Contains update data - PayloadSpec and extra properties list.
* *
* <p>{@code mPayload} contains url, offset and size to {@code PAYLOAD_BINARY_FILE_NAME}. * <p>{@code mPayload} contains url, offset and size to {@code PAYLOAD_BINARY_FILE_NAME}.

View file

@ -28,6 +28,7 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.RecoverySystem; import android.os.RecoverySystem;
import android.os.ResultReceiver; import android.os.ResultReceiver;
import android.os.UpdateEngine;
import android.util.Log; import android.util.Log;
import com.example.android.systemupdatersample.PayloadSpec; import com.example.android.systemupdatersample.PayloadSpec;
@ -49,10 +50,10 @@ import java.util.Optional;
* without downloading the whole package. And it constructs {@link PayloadSpec}. * without downloading the whole package. And it constructs {@link PayloadSpec}.
* All this work required to install streaming A/B updates. * All this work required to install streaming A/B updates.
* *
* PrepareStreamingService runs on it's own thread. It will notify activity * PrepareUpdateService runs on it's own thread. It will notify activity
* using interface {@link UpdateResultCallback} when update is ready to install. * using interface {@link UpdateResultCallback} when update is ready to install.
*/ */
public class PrepareStreamingService extends IntentService { public class PrepareUpdateService extends IntentService {
/** /**
* UpdateResultCallback result codes. * UpdateResultCallback result codes.
@ -61,22 +62,27 @@ public class PrepareStreamingService extends IntentService {
public static final int RESULT_CODE_ERROR = 1; public static final int RESULT_CODE_ERROR = 1;
/** /**
* This interface is used to send results from {@link PrepareStreamingService} to * Extra params that will be sent to IntentService.
*/
public static final String EXTRA_PARAM_CONFIG = "config";
public static final String EXTRA_PARAM_RESULT_RECEIVER = "result-receiver";
/**
* This interface is used to send results from {@link PrepareUpdateService} to
* {@code MainActivity}. * {@code MainActivity}.
*/ */
public interface UpdateResultCallback { public interface UpdateResultCallback {
/** /**
* Invoked when files are downloaded and payload spec is constructed. * Invoked when files are downloaded and payload spec is constructed.
* *
* @param resultCode result code, values are defined in {@link PrepareStreamingService} * @param resultCode result code, values are defined in {@link PrepareUpdateService}
* @param payloadSpec prepared payload spec for streaming update * @param payloadSpec prepared payload spec for streaming update
*/ */
void onReceiveResult(int resultCode, PayloadSpec payloadSpec); void onReceiveResult(int resultCode, PayloadSpec payloadSpec);
} }
/** /**
* Starts PrepareStreamingService. * Starts PrepareUpdateService.
* *
* @param context application context * @param context application context
* @param config update config * @param config update config
@ -84,26 +90,21 @@ public class PrepareStreamingService extends IntentService {
*/ */
public static void startService(Context context, public static void startService(Context context,
UpdateConfig config, UpdateConfig config,
Handler handler,
UpdateResultCallback resultCallback) { UpdateResultCallback resultCallback) {
Log.d(TAG, "Starting PrepareStreamingService"); Log.d(TAG, "Starting PrepareUpdateService");
ResultReceiver receiver = new CallbackResultReceiver(new Handler(), resultCallback); ResultReceiver receiver = new CallbackResultReceiver(handler, resultCallback);
Intent intent = new Intent(context, PrepareStreamingService.class); Intent intent = new Intent(context, PrepareUpdateService.class);
intent.putExtra(EXTRA_PARAM_CONFIG, config); intent.putExtra(EXTRA_PARAM_CONFIG, config);
intent.putExtra(EXTRA_PARAM_RESULT_RECEIVER, receiver); intent.putExtra(EXTRA_PARAM_RESULT_RECEIVER, receiver);
context.startService(intent); context.startService(intent);
} }
public PrepareStreamingService() { public PrepareUpdateService() {
super(TAG); super(TAG);
} }
private static final String TAG = "PrepareStreamingService"; private static final String TAG = "PrepareUpdateService";
/**
* Extra params that will be sent from Activity to IntentService.
*/
private static final String EXTRA_PARAM_CONFIG = "config";
private static final String EXTRA_PARAM_RESULT_RECEIVER = "result-receiver";
/** /**
* The files that should be downloaded before streaming. * The files that should be downloaded before streaming.
@ -117,6 +118,7 @@ public class PrepareStreamingService extends IntentService {
); );
private final PayloadSpecs mPayloadSpecs = new PayloadSpecs(); private final PayloadSpecs mPayloadSpecs = new PayloadSpecs();
private final UpdateEngine mUpdateEngine = new UpdateEngine();
@Override @Override
protected void onHandleIntent(Intent intent) { protected void onHandleIntent(Intent intent) {
@ -142,6 +144,10 @@ public class PrepareStreamingService extends IntentService {
private PayloadSpec execute(UpdateConfig config) private PayloadSpec execute(UpdateConfig config)
throws IOException, PreparationFailedException { throws IOException, PreparationFailedException {
if (config.getInstallType() == UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING) {
return mPayloadSpecs.forNonStreaming(config.getUpdatePackageFile());
}
downloadPreStreamingFiles(config, OTA_PACKAGE_DIR); downloadPreStreamingFiles(config, OTA_PACKAGE_DIR);
Optional<UpdateConfig.PackageFile> payloadBinary = Optional<UpdateConfig.PackageFile> payloadBinary =
@ -176,6 +182,7 @@ public class PrepareStreamingService extends IntentService {
* Downloads files defined in {@link UpdateConfig#getAbConfig()} * Downloads files defined in {@link UpdateConfig#getAbConfig()}
* and exists in {@code PRE_STREAMING_FILES_SET}, and put them * and exists in {@code PRE_STREAMING_FILES_SET}, and put them
* in directory {@code dir}. * in directory {@code dir}.
*
* @throws IOException when can't download a file * @throws IOException when can't download a file
*/ */
private void downloadPreStreamingFiles(UpdateConfig config, String dir) private void downloadPreStreamingFiles(UpdateConfig config, String dir)
@ -212,7 +219,7 @@ public class PrepareStreamingService extends IntentService {
} }
/** /**
* Used by {@link PrepareStreamingService} to pass {@link PayloadSpec} * Used by {@link PrepareUpdateService} to pass {@link PayloadSpec}
* to {@link UpdateResultCallback#onReceiveResult}. * to {@link UpdateResultCallback#onReceiveResult}.
*/ */
private static class CallbackResultReceiver extends ResultReceiver { private static class CallbackResultReceiver extends ResultReceiver {

View file

@ -21,6 +21,7 @@ import android.app.AlertDialog;
import android.graphics.Color; import android.graphics.Color;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.UpdateEngine; import android.os.UpdateEngine;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
@ -34,7 +35,6 @@ import com.example.android.systemupdatersample.R;
import com.example.android.systemupdatersample.UpdateConfig; import com.example.android.systemupdatersample.UpdateConfig;
import com.example.android.systemupdatersample.UpdateManager; import com.example.android.systemupdatersample.UpdateManager;
import com.example.android.systemupdatersample.UpdaterState; import com.example.android.systemupdatersample.UpdaterState;
import com.example.android.systemupdatersample.util.PayloadSpecs;
import com.example.android.systemupdatersample.util.UpdateConfigs; import com.example.android.systemupdatersample.util.UpdateConfigs;
import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes; import com.example.android.systemupdatersample.util.UpdateEngineErrorCodes;
import com.example.android.systemupdatersample.util.UpdateEngineStatuses; import com.example.android.systemupdatersample.util.UpdateEngineStatuses;
@ -67,7 +67,7 @@ public class MainActivity extends Activity {
private List<UpdateConfig> mConfigs; private List<UpdateConfig> mConfigs;
private final UpdateManager mUpdateManager = private final UpdateManager mUpdateManager =
new UpdateManager(new UpdateEngine(), new PayloadSpecs()); new UpdateManager(new UpdateEngine(), new Handler());
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {

View file

@ -30,7 +30,7 @@ import java.net.URLConnection;
* Downloads chunk of a file from given url using {@code offset} and {@code size}, * Downloads chunk of a file from given url using {@code offset} and {@code size},
* and saves to a given location. * and saves to a given location.
* *
* In real-life application this helper class should download from HTTP Server, * In a real-life application this helper class should download from HTTP Server,
* but in this sample app it will only download from a local file. * but in this sample app it will only download from a local file.
*/ */
public final class FileDownloader { public final class FileDownloader {

View file

@ -18,20 +18,25 @@ package com.example.android.systemupdatersample;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.os.UpdateEngine; import android.os.UpdateEngine;
import android.os.UpdateEngineCallback; import android.os.UpdateEngineCallback;
import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import com.example.android.systemupdatersample.services.PrepareUpdateService;
import com.example.android.systemupdatersample.tests.R; import com.example.android.systemupdatersample.tests.R;
import com.example.android.systemupdatersample.util.PayloadSpecs;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.io.CharStreams; import com.google.common.io.CharStreams;
@ -43,7 +48,6 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoRule;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -60,49 +64,39 @@ public class UpdateManagerTest {
@Mock @Mock
private UpdateEngine mUpdateEngine; private UpdateEngine mUpdateEngine;
@Mock @Mock
private PayloadSpecs mPayloadSpecs; private Context mMockContext;
private UpdateManager mSubject; private UpdateManager mSubject;
private Context mContext; private Context mTestContext;
private UpdateConfig mNonStreamingUpdate003; private UpdateConfig mStreamingUpdate002;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext(); mTestContext = InstrumentationRegistry.getContext();
mSubject = new UpdateManager(mUpdateEngine, mPayloadSpecs); mSubject = new UpdateManager(mUpdateEngine, null);
mNonStreamingUpdate003 = mStreamingUpdate002 =
UpdateConfig.fromJson(readResource(R.raw.update_config_003_nonstream)); UpdateConfig.fromJson(readResource(R.raw.update_config_002_stream));
} }
@Test @Test
public void applyUpdate_appliesPayloadToUpdateEngine() throws Exception { public void applyUpdate_appliesPayloadToUpdateEngine() throws Exception {
PayloadSpec payload = buildMockPayloadSpec(); mockContextStartServiceAnswer(buildMockPayloadSpec());
when(mPayloadSpecs.forNonStreaming(any(File.class))).thenReturn(payload); mSubject.applyUpdate(mMockContext, mStreamingUpdate002);
when(mUpdateEngine.bind(any(UpdateEngineCallback.class))).thenAnswer(answer -> {
// When UpdateManager is bound to update_engine, it passes
// UpdateEngineCallback as a callback to update_engine.
UpdateEngineCallback callback = answer.getArgument(0);
callback.onStatusUpdate(
UpdateEngine.UpdateStatusConstants.IDLE,
/*engineProgress*/ 0.0f);
return null;
});
mSubject.bind();
mSubject.applyUpdate(null, mNonStreamingUpdate003);
verify(mUpdateEngine).applyPayload( verify(mUpdateEngine).applyPayload(
"file://blah", "file://blah",
120, 120,
340, 340,
new String[]{ new String[]{
"SWITCH_SLOT_ON_REBOOT=0" // ab_config.force_switch_slot = false "SWITCH_SLOT_ON_REBOOT=0", // ab_config.force_switch_slot = false
"USER_AGENT=" + UpdateManager.HTTP_USER_AGENT
}); });
} }
@Test @Test
public void stateIsRunningAndEngineStatusIsIdle_reApplyLastUpdate() throws Exception { @UiThreadTest
PayloadSpec payload = buildMockPayloadSpec(); public void stateIsRunningAndEngineStatusIsIdle_reApplyLastUpdate() throws Throwable {
when(mPayloadSpecs.forNonStreaming(any(File.class))).thenReturn(payload); mockContextStartServiceAnswer(buildMockPayloadSpec());
// UpdateEngine always returns IDLE status.
when(mUpdateEngine.bind(any(UpdateEngineCallback.class))).thenAnswer(answer -> { when(mUpdateEngine.bind(any(UpdateEngineCallback.class))).thenAnswer(answer -> {
// When UpdateManager is bound to update_engine, it passes // When UpdateManager is bound to update_engine, it passes
// UpdateEngineCallback as a callback to update_engine. // UpdateEngineCallback as a callback to update_engine.
@ -114,21 +108,36 @@ public class UpdateManagerTest {
}); });
mSubject.bind(); mSubject.bind();
mSubject.applyUpdate(null, mNonStreamingUpdate003); mSubject.applyUpdate(mMockContext, mStreamingUpdate002);
mSubject.unbind(); mSubject.unbind();
mSubject.bind(); // re-bind - now it should re-apply last update mSubject.bind(); // re-bind - now it should re-apply last update
assertEquals(mSubject.getUpdaterState(), UpdaterState.RUNNING); assertEquals(mSubject.getUpdaterState(), UpdaterState.RUNNING);
// it should be called 2 times
verify(mUpdateEngine, times(2)).applyPayload( verify(mUpdateEngine, times(2)).applyPayload(
"file://blah", "file://blah",
120, 120,
340, 340,
new String[]{ new String[]{
"SWITCH_SLOT_ON_REBOOT=0" // ab_config.force_switch_slot = false "SWITCH_SLOT_ON_REBOOT=0", // ab_config.force_switch_slot = false
"USER_AGENT=" + UpdateManager.HTTP_USER_AGENT
}); });
} }
private void mockContextStartServiceAnswer(PayloadSpec payloadSpec) {
doAnswer(args -> {
Intent intent = args.getArgument(0);
ResultReceiver resultReceiver = intent.getParcelableExtra(
PrepareUpdateService.EXTRA_PARAM_RESULT_RECEIVER);
Bundle b = new Bundle();
b.putSerializable(
/* PrepareUpdateService.CallbackResultReceiver.BUNDLE_PARAM_PAYLOAD_SPEC */
"payload-spec",
payloadSpec);
resultReceiver.send(PrepareUpdateService.RESULT_CODE_SUCCESS, b);
return null;
}).when(mMockContext).startService(any(Intent.class));
}
private PayloadSpec buildMockPayloadSpec() { private PayloadSpec buildMockPayloadSpec() {
PayloadSpec payload = mock(PayloadSpec.class); PayloadSpec payload = mock(PayloadSpec.class);
when(payload.getUrl()).thenReturn("file://blah"); when(payload.getUrl()).thenReturn("file://blah");
@ -140,7 +149,7 @@ public class UpdateManagerTest {
private String readResource(int id) throws IOException { private String readResource(int id) throws IOException {
return CharStreams.toString(new InputStreamReader( return CharStreams.toString(new InputStreamReader(
mContext.getResources().openRawResource(id))); mTestContext.getResources().openRawResource(id)));
} }
} }